/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    QC_GetOpt.qpp

    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include <qore/Qore.h>
#include "qore/intern/QC_GetOpt.h"

#include <cstring>

static inline int process_type(const char* key, int& attributes, char* opt, qore_type_t& at, ExceptionSink* xsink) {
    assert(at == -1);
    const char* type_name = 0;
    // get type
    switch (*opt) {
        case 's':
            at = NT_STRING;
            type_name = QoreStringNode::getStaticTypeName();
            break;
        case 'i':
            at = NT_INT;
            type_name = qoreIntTypeName;
            break;
        case 'f':
            at = NT_FLOAT;
            type_name = qoreFloatTypeName;
            break;
        case 'b':
            at = NT_BOOLEAN;
            type_name = qoreBoolTypeName;
            break;
        case 'd':
            at = NT_DATE;
            type_name = DateTimeNode::getStaticTypeName();
            break;
        case 'h':
            at = NT_HASH;
            type_name = QoreHashNode::getStaticTypeName();
            break;
        case '@':
            at = NT_STRING;
            attributes |= QGO_OPT_LIST;
            type_name = QoreStringNode::getStaticTypeName();
            break;
        case '+':
            at = NT_INT;
            attributes |= QGO_OPT_ADDITIVE;
            type_name = qoreIntTypeName;
            break;
    }
    if (at == -1) {
        xsink->raiseException("GETOPT-OPTION-ERROR", "type '%c' for key '%s' is unknown", *opt, key);
        return -1;
    }
    if (!opt[1])
        return 0;

    if (opt[2]) {
        xsink->raiseException("GETOPT-OPTION-ERROR", "invalid attributes in option '%s'", key);
        return -1;
    }

    // process modifiers
    if (opt[1] == '@') {
        if (attributes & QGO_OPT_LIST) {
            xsink->raiseException("GETOPT-OPTION-ERROR", "list attribute doubled in option key '%s'", key);
            return -1;
        }
        if (attributes & QGO_OPT_ADDITIVE) {
            xsink->raiseException("GETOPT-OPTION-ERROR", "option '%s' cannot have both additive and list attributes turned on", key);
            return -1;
        }
        attributes |= QGO_OPT_LIST;
        return 0;
    }
    if (opt[1] == '+') {
        if (attributes & QGO_OPT_ADDITIVE) {
            xsink->raiseException("GETOPT-OPTION-ERROR", "additive attribute doubled in option key '%s'", key);
            return -1;
        }
        if (attributes & QGO_OPT_LIST) {
            xsink->raiseException("GETOPT-OPTION-ERROR", "option '%s' cannot have both additive and list attributes turned on", key);
            return -1;
        }
        if (at != NT_INT && at != NT_FLOAT) {
            xsink->raiseException("GETOPT-OPTION-ERROR", "additive attributes for type '%s' are not supported (option '%s')", type_name, key);
            return -1;
        }
        attributes |= QGO_OPT_ADDITIVE;
        return 0;
    }

    xsink->raiseException("GETOPT-OPTION-ERROR", "unknown modifier '%c' in option '%s'", opt[1], key);
    return -1;
}

static AbstractQoreNode* GETOPT_parse2_intern(ReferenceHolder<QoreHashNode>& rv, ExceptionSink* xsink) {
    assert(rv);

    // check for _ERRORS_ key
    const QoreListNode* l = rv->getKeyValue("_ERRORS_").get<const QoreListNode>();
    if (!l)
        return rv.release();

    const QoreStringNode* err = l->retrieveEntry(0).get<const QoreStringNode>();
    return xsink->raiseException("GETOPT-ERROR", err->stringRefSelf());
}

static AbstractQoreNode* GETOPT_parse3_intern(ReferenceHolder<QoreHashNode>& rv, ExceptionSink* xsink) {
    assert(rv);

    // check for _ERRORS_ key
    const QoreListNode* l = rv->getKeyValue("_ERRORS_").get<const QoreListNode>();
    if (!l)
        return rv.release();

    const QoreStringNode* err = l->retrieveEntry(0).get<const QoreStringNode>();
    const QoreStringNode* sn = getProgram()->getScriptName();
    if (sn)
        fprintf(stderr, "%s: ", sn->getBuffer());
    fprintf(stderr, "%s\n", err->getBuffer());
    qore_exit_process(1);

    return 0;
}

static GetOpt* GETOPT_constructor_intern(const QoreHashNode* options, ExceptionSink* xsink) {
    SimpleRefHolder<GetOpt> g(new GetOpt);

    ConstHashIterator hi(options);
    QoreString vstr;
    while (hi.next()) {
        const char *k = hi.getKey();
        if (!strcmp(k, "_ERRORS_")) {
            xsink->raiseException("GETOPT-PARAMETER-ERROR", "option key '%s' is reserved for errors in the output hash", k);
            break;
        }

        const QoreValue v = hi.get();
        if (v.getType() != NT_STRING) {
            xsink->raiseException("GETOPT-PARAMETER-ERROR", "value of option key '%s' is not a string (%s)", k, v.getTypeName());
            break;
        }

        const QoreStringNode* str = v.get<const QoreStringNode>();

        qore_type_t at = -1;
        const char *long_opt = 0;
        char short_opt = '\0';
        int attributes = QGO_OPT_NONE;

        // reset buffer
        vstr.clear();
        vstr.concat(str->getBuffer());
        const char* val = vstr.getBuffer();

        // get data type, if any
        char *tok = strchrs(val, "=:");
        if (tok) {
            if (tok[1] && process_type(k, attributes, tok + 1, at, xsink))
                break;
            if ((*tok) == '=')
                attributes |= QGO_OPT_MANDATORY;
            else if (attributes & QGO_OPT_LIST) {
                xsink->raiseException("GETOPT-OPTION-ERROR", "option '%s' takes a list and therefore must have mandatory arguments", k);
                break;
            }

            (*tok) = '\0';
        }
        // get option names
        if (!val[0]) {
            //printd(5, "making exception key='%s' tok=%p val=%p val='%s'\n", k, tok, val, val);
            xsink->raiseException("GETOPT-PARAMETER-ERROR", "value of option key '%s' has no option specifiers", k);
            break;
        }
        tok = (char*)strchr(val, ',');
        if (tok) {
            if (tok == (val + 1)) {
                short_opt = val[0];
                long_opt = val + 2;
            } else if (tok - val == (signed)(strlen(val) - 2)) {
                (*tok) = 0;
                short_opt = tok[1];
                long_opt = val;
            } else { // if the comma is not in the second or second-to-last position, then it's an error
                xsink->raiseException("GETOPT-OPTION-ERROR", "user options can only be specified with one short option and one long option, however two long options were given for key '%s' (%s)", k, val);
                break;
            }
        }
        else if (val[1])
            long_opt = val;
        else
            short_opt = val[0];
        int rc = g->add(k, short_opt, long_opt, at, attributes);
        if (rc == QGO_ERR_DUP_SHORT_OPT) {
            xsink->raiseException("GETOPT-OPTION-ERROR", "short option '%c' was duplicated in key '%s'", short_opt, k);
            break;
        }
        if (rc == QGO_ERR_DUP_LONG_OPT) {
            xsink->raiseException("GETOPT-OPTION-ERROR", "long option '%s' was duplicated in key '%s'", long_opt, k);
            break;
        }
        if (rc == QGO_ERR_DUP_NAME) {
            xsink->raiseException("GETOPT-OPTION-ERROR", "option '%s' was duplicated", k);
            break;
        }
    }

    return *xsink ? nullptr : g.release();
}

//! The %GetOpt class provides an easy way to process POSIX-style command-line options in %Qore scripts/programs
/**
 */
qclass GetOpt [arg=GetOpt* g];

//! Creates the GetOpt object and sets the option hash with the single required argument
/** @param options Each key defines the key value for the return hash if any arguments are given corresponding to the string value of the key; The string value of each hash key follows the following pattern:\n
    <em>opts</em><tt>[(=|:)</tt><em>type</em><tt>[</tt><em>modifier</em><tt>]]</tt>\n
    Where the meaning of the above placeholders in italics is:\n
    - <em>opts</em>: At least one short option and/or a long option name; if both are present, then they must be separated by a comma. The short option must be a single character.
    - <tt>(=|:)</tt><em>type</em>: if \c "=" is used, then the option takes a mandatory argument, if \c ":" is used, then the argument is optional. Types are specified as follows:
      - <em>s</em>: string
      - <em>i</em>: integer
      - <em>f</em>: float
      - <em>d</em>: date
      - <em>b</em>: boolean
    - <em>modifier</em>: \c "@" specifies a list, \c "+" an additive value (sum; must be integer or float type)

    @par Example:
    @code{.py}
const ProgramPptions = (
     "url"  : "u,url=s",
     "xml"  : "x,xml",
     "lxml" : "X,literal-xml",
     "verb" : "v,verbose",
     "help" : "h,help",
 );

GetOpt getopt(ProgramOptions);
    @endcode

    @throw GETOPT-PARAMETER-ERROR option key value is not a string; \c "_ERRORS_" used as option key
    @throw GETOPT-OPTION-ERROR list option specified as optional; empty option key value string; multiple long options given for key; duplicate options given; invalid attributes for option; unknown modifier given for option
 */
GetOpt::constructor(hash<auto> options) {
    SimpleRefHolder<GetOpt> g(GETOPT_constructor_intern(options, xsink));
    if (*xsink) {
        return;
    }
    self->setPrivate(CID_GETOPT, g.release());
}

//! Throws an exception; objects of this class cannot be copied
/**
    @throw GETOPT-COPY-ERROR copying GetOpt objects is not supported
 */
GetOpt::copy() {
   xsink->raiseException("GETOPT-COPY-ERROR", "copying GetOpt objects is not supported");
}

//! Parses the parameter list according to the option hash passed to the constructor
/** All arguments parsed will be removed from the list reference passed as the sole argument, leaving only unparsed arguments (for example, file names).

    If any errors are encountered, the return value hash will have a key \c "_ERRORS_" giving a list of error messages pertaining to the options parsed.

    @param pgm_args The reference should point to a list of arguments to process (normally <tt>ARGV</tt>); any argument accepted by the object will be removed from the list

    @return A hash keyed by option names (as given in the hash to the GetOpt constructor), where each key's value is the value of the argument passed in the list argument. The hash key \c "_ERRORS_" will contain any errors.

    @par Example:
    @code{.py}
hash<auto> o = getopt.parse(\ARGV);
if (exists o."_ERRORS_") {
    foreach string err in (o."_ERRORS_")
        stderr.printf("%s\n", err);
    exit(1);
}
    @endcode

    @see GetOpt::parse2() for a similar method that throws an exception instead of putting error information in the \c "_ERRORS_" key of the hash value returned
 */
hash<auto> GetOpt::parse(reference<list<string>> pgm_args) {
    QoreTypeSafeReferenceHelper ref(pgm_args, xsink);
    if (!ref)
        return QoreValue();

    if (ref.getType() != NT_LIST)
        return new QoreHashNode(autoTypeInfo);

    QoreListNode* l = reinterpret_cast<QoreListNode*>(ref.getUnique(xsink));
    if (*xsink)
        return QoreValue();

    return g->parse(l, true, xsink);
}

//! Parses the parameter list according to the option hash passed to the constructor
/** If any errors are encountered, the return value hash will have a key \c "_ERRORS_" giving a list of error messages pertaining to the options parsed.

    @param pgm_args A list of arguments to process

    @return A hash keyed by option names (as given in the hash to the GetOpt constructor), where each key's value is the value of the argument passed in the list argument. The hash key \c "_ERRORS_" will contain any errors.

    @par Example:
    @code{.py}
hash<auto> o = getopt.parse(ARGV);
if (o._ERRORS_) {
    map stderr.printf("%s\n", $1), o._ERRORS_;
    exit(1);
}
    @endcode

    @see GetOpt::parse2() for a similar method that throws an exception instead of putting error information in the \c "_ERRORS_" key of the hash value returned
 */
hash<auto> GetOpt::parse(softlist<auto> pgm_args) {
    return g->parse(const_cast<QoreListNode*>(pgm_args), false, xsink);
}

//! Parses the parameter list according to the option hash passed to the constructor
/** If any errors are encountered, an appropriate exception will be thrown.

    @param pgm_args The reference should point to a list of arguments to process (normally <tt>ARGV</tt>); any argument accepted by the object will be removed from the list

    @return A hash keyed by option names (as given in the hash to the GetOpt constructor), where each key's value is the value of the argument passed in the list argument

    @par Example:
    @code{.py}
try {
   hash<auto> o = getopt.parse2(\ARGV);
} catch (hash<ExceptionInfo> ex) {
   stderr.printf("%s\n", ex.desc);
   exit(1);
}
    @endcode

    @throw GETOPT-ERROR error parsing arguments

    @see GetOpt::parse() for a similar method that puts error information in the \c "_ERRORS_" key of the hash value returned instead of throwing an exception
 */
hash<auto> GetOpt::parse2(reference<list<string>> pgm_args) {
    QoreTypeSafeReferenceHelper ref(pgm_args, xsink);
    if (!ref)
        return QoreValue();

    if (ref.getType() != NT_LIST)
        return new QoreHashNode(autoTypeInfo);

    QoreListNode* l = reinterpret_cast<QoreListNode*>(ref.getUnique(xsink));
    if (*xsink)
        return QoreValue();

    ReferenceHolder<QoreHashNode> rv(g->parse(l, true, xsink), xsink);
    return *xsink ? QoreValue() : GETOPT_parse2_intern(rv, xsink);
}

//! Parses the parameter list according to the option hash passed to the constructor
/** If any errors are encountered, an appropriate exception will be thrown.

    @param pgm_args A list of arguments to process

    @return A hash keyed by option names (as given in the hash to the GetOpt constructor), where each key's value is the value of the argument passed in the list argument

    @par Example:
    @code{.py}
try {
   hash<auto> o = getopt.parse2(ARGV);
} catch (hash<ExceptionInfo> ex) {
   stderr.printf("%s\n", ex.desc);
   exit(1);
}
    @endcode

    @throw GETOPT-ERROR error parsing arguments

    @see GetOpt::parse() for a similar method that puts error information in the \c "_ERRORS_" key of the hash value returned instead of throwing an exception
 */
hash<auto> GetOpt::parse2(softlist<auto> pgm_args) {
    ReferenceHolder<QoreHashNode> rv(g->parse(const_cast<QoreListNode*>(pgm_args), false, xsink), xsink);
    return *xsink ? 0 : GETOPT_parse2_intern(rv, xsink);
}

//! Parses the parameter list according to the option hash passed to the constructor and displays an explanatory error message on stderr and exits the program if an error occurs
/** @param pgm_args The reference should point to a list of arguments to process (normally <tt>ARGV</tt>); any argument accepted by the object will be removed from the list

    @return A hash keyed by option names (as given in the hash to the GetOpt constructor), where each key's value is the value of the argument passed in the list argument

    @par Example:
    @code{.py}
hash<auto> o = getopt.parse3(\ARGV);
    @endcode

    @see
    - GetOpt::parse() for a similar method that puts error information in the \c "_ERRORS_" key of the hash value returned
    - GetOpt::parse2() for a similar method that throws exceptions instead of exiting the program
 */
hash<auto> GetOpt::parse3(reference<list<string>> pgm_args) [dom=PROCESS] {
    QoreTypeSafeReferenceHelper ref(pgm_args, xsink);
    if (!ref)
        return QoreValue();

    if (ref.getType() != NT_LIST)
        return new QoreHashNode(autoTypeInfo);

    QoreListNode* l = reinterpret_cast<QoreListNode*>(ref.getUnique(xsink));
    if (*xsink)
        return QoreValue();

    ReferenceHolder<QoreHashNode> rv(g->parse(l, true, xsink), xsink);
    return *xsink ? 0 : GETOPT_parse3_intern(rv, xsink);
}

//! Parses the parameter list according to the option hash passed to the constructor and displays an explanatory error message on stderr and exits the program if an error occurs
/** @param pgm_args A list of arguments to process

    @return A hash keyed by option names (as given in the hash to the GetOpt constructor), where each key's value is the value of the argument passed in the list argument

    @par Example:
    @code{.py}
hash<auto> o = getopt.parse3(ARGV);
    @endcode

    @throw GETOPT-ERROR error parsing arguments

    @see
    - GetOpt::parse() for a similar method that puts error information in the \c "_ERRORS_" key of the hash value returned
    - GetOpt::parse2() for a similar method that throws exceptions instead of exiting the program
 */
hash<auto> GetOpt::parse3(softlist<auto> pgm_args) [dom=PROCESS] {
    ReferenceHolder<QoreHashNode> rv(g->parse(const_cast<QoreListNode*>(pgm_args), false, xsink), xsink);
    return *xsink ? 0 : GETOPT_parse3_intern(rv, xsink);
}

//! Parses the given options and returns a hash of the parsed options
/** @par Example:
    @code{.py}
const Opts = {
     "url"  : "u,url=s",
     "xml"  : "x,xml",
     "lxml" : "X,literal-xml",
     "verb" : "v,verbose",
     "help" : "h,help",
};
hash<auto> o = GetOpt::parse(Opts, \ARGV);
if (exists o."_ERRORS_") {
    foreach string err in (o."_ERRORS_")
        stderr.printf("%s\n", err);
    exit(1);
}
    @endcode

    @param opts a hash where each key defines the key value for the return hash if any arguments are given
    corresponding to the string value of the key; The string value of each hash key follows the following pattern:\n
    <em>opts</em><tt>[(=|:)</tt><em>type</em><tt>[</tt><em>modifier</em><tt>]]</tt>\n
    Where the meaning of the above placeholders in italics is:\n
    - <em>opts</em>: At least one short option and/or a long option name; if both are present, then they must be
      separated by a comma. The short option must be a single character.
    - <tt>(=|:)</tt><em>type</em>: if \c "=" is used, then the option takes a mandatory argument, if \c ":" is used,
      then the argument is optional. Types are specified as follows:
      - <em>s</em>: string
      - <em>i</em>: integer
      - <em>f</em>: float
      - <em>d</em>: date
      - <em>b</em>: boolean
    - <em>modifier</em>: \c "@" specifies a list, \c "+" an additive value (sum; must be integer or float type)
    @param pgm_args The reference should point to a list of arguments to process (normally <tt>ARGV</tt>); any
    argument accepted by the object will be removed from the list

    @return A hash keyed by option names (as given in the hash to the GetOpt constructor), where each key's value is
    the value of the argument passed in the list argument. The hash key \c "_ERRORS_" will contain any errors.

    All arguments parsed will be removed from \a pgm_args, leaving only unparsed arguments (for example, file names).

    If any errors are encountered, the return value hash will have a key \c "_ERRORS_" giving a list of error messages
    pertaining to the options parsed.

    @see
    - @ref Qore::GetOpt::parseEx() "GetOpt::parseEx(hash<auto>, reference<list<string>>)"
    - @ref Qore::GetOpt::parseExit() "GetOpt::parseExit(hash<auto>, reference<list<string>>)"
*/
static hash<auto> GetOpt::parse(hash<auto> opts, reference<list<string>> pgm_args) {
    SimpleRefHolder<GetOpt> g(GETOPT_constructor_intern(opts, xsink));
    if (*xsink) {
        return QoreValue();
    }

    QoreTypeSafeReferenceHelper ref(pgm_args, xsink);
    if (!ref) {
        return QoreValue();
    }

    if (ref.getType() != NT_LIST) {
        return new QoreHashNode(autoTypeInfo);
    }

    QoreListNode* l = reinterpret_cast<QoreListNode*>(ref.getUnique(xsink));
    if (*xsink) {
        return QoreValue();
    }

    return g->parse(l, true, xsink);
}

//! Parses the given options and returns a hash of the parsed options and throws and exception if there are any errors
/** @par Example:
    @code{.py}
const Opts = {
     "url"  : "u,url=s",
     "xml"  : "x,xml",
     "lxml" : "X,literal-xml",
     "verb" : "v,verbose",
     "help" : "h,help",
};
hash<auto> o = GetOpt::parseEx(Opts, \ARGV);
    @endcode

    @param opts a hash where each key defines the key value for the return hash if any arguments are given
    corresponding to the string value of the key; The string value of each hash key follows the following pattern:\n
    <em>opts</em><tt>[(=|:)</tt><em>type</em><tt>[</tt><em>modifier</em><tt>]]</tt>\n
    Where the meaning of the above placeholders in italics is:\n
    - <em>opts</em>: At least one short option and/or a long option name; if both are present, then they must be
      separated by a comma. The short option must be a single character.
    - <tt>(=|:)</tt><em>type</em>: if \c "=" is used, then the option takes a mandatory argument, if \c ":" is used,
      then the argument is optional. Types are specified as follows:
      - <em>s</em>: string
      - <em>i</em>: integer
      - <em>f</em>: float
      - <em>d</em>: date
      - <em>b</em>: boolean
    - <em>modifier</em>: \c "@" specifies a list, \c "+" an additive value (sum; must be integer or float type)
    @param pgm_args The reference should point to a list of arguments to process (normally <tt>ARGV</tt>); any
    argument accepted by the object will be removed from the list

    @return A hash keyed by option names (as given in the hash to the GetOpt constructor), where each key's value is
    the value of the argument passed in the list argument.  An exception is thrown if there are any errors.

    All arguments parsed will be removed from \a pgm_args, leaving only unparsed arguments (for example, file names).

    If any errors are encountered, a \c GETOPT-ERROR exception is thrown.

    @throw GETOPT-ERROR error parsing arguments

    @see
    - @ref Qore::GetOpt::parse() "GetOpt::parse(hash<auto>, reference<list<string>>)"
    - @ref Qore::GetOpt::parseExit() "GetOpt::parseExit(hash<auto>, reference<list<string>>)"
*/
static hash<auto> GetOpt::parseEx(hash<auto> opts, reference<list<string>> pgm_args) {
    SimpleRefHolder<GetOpt> g(GETOPT_constructor_intern(opts, xsink));
    if (*xsink) {
        return QoreValue();
    }

    QoreTypeSafeReferenceHelper ref(pgm_args, xsink);
    if (!ref) {
        return QoreValue();
    }

    if (ref.getType() != NT_LIST) {
        return new QoreHashNode(autoTypeInfo);
    }

    QoreListNode* l = reinterpret_cast<QoreListNode*>(ref.getUnique(xsink));
    if (*xsink) {
        return QoreValue();
    }

    ReferenceHolder<QoreHashNode> rv(g->parse(l, true, xsink), xsink);
    return *xsink ? QoreValue() : GETOPT_parse2_intern(rv, xsink);
}

//! Parses the given options and returns a hash of the parsed options; prints out an error message and exits the program if there are any errors
/** @par Example:
    @code{.py}
const Opts = {
     "url"  : "u,url=s",
     "xml"  : "x,xml",
     "lxml" : "X,literal-xml",
     "verb" : "v,verbose",
     "help" : "h,help",
};
hash<auto> o = GetOpt::parseExit(Opts, \ARGV);
    @endcode

    @param opts a hash where each key defines the key value for the return hash if any arguments are given
    corresponding to the string value of the key; The string value of each hash key follows the following pattern:\n
    <em>opts</em><tt>[(=|:)</tt><em>type</em><tt>[</tt><em>modifier</em><tt>]]</tt>\n
    Where the meaning of the above placeholders in italics is:\n
    - <em>opts</em>: At least one short option and/or a long option name; if both are present, then they must be
      separated by a comma. The short option must be a single character.
    - <tt>(=|:)</tt><em>type</em>: if \c "=" is used, then the option takes a mandatory argument, if \c ":" is used,
      then the argument is optional. Types are specified as follows:
      - <em>s</em>: string
      - <em>i</em>: integer
      - <em>f</em>: float
      - <em>d</em>: date
      - <em>b</em>: boolean
    - <em>modifier</em>: \c "@" specifies a list, \c "+" an additive value (sum; must be integer or float type)
    @param pgm_args The reference should point to a list of arguments to process (normally <tt>ARGV</tt>); any
    argument accepted by the object will be removed from the list

    @return A hash keyed by option names (as given in the hash to the GetOpt constructor), where each key's value is
    the value of the argument passed in the list argument.  Prints out an error message and exits the program if there
    are any errors.

    All arguments parsed will be removed from \a pgm_args, leaving only unparsed arguments (for example, file names).

    @see
    - @ref Qore::GetOpt::parse() "GetOpt::parse(hash<auto>, reference<list<string>>)"
    - @ref Qore::GetOpt::parseEx() "GetOpt::parseEx(hash<auto>, reference<list<string>>)"
*/
static hash<auto> GetOpt::parseExit(hash<auto> opts, reference<list<string>> pgm_args) [dom=PROCESS] {
    SimpleRefHolder<GetOpt> g(GETOPT_constructor_intern(opts, xsink));
    if (*xsink) {
        return QoreValue();
    }

    QoreTypeSafeReferenceHelper ref(pgm_args, xsink);
    if (!ref) {
        return QoreValue();
    }

    if (ref.getType() != NT_LIST) {
        return new QoreHashNode(autoTypeInfo);
    }

    QoreListNode* l = reinterpret_cast<QoreListNode*>(ref.getUnique(xsink));
    if (*xsink) {
        return QoreValue();
    }

    ReferenceHolder<QoreHashNode> rv(g->parse(l, true, xsink), xsink);
    return *xsink ? QoreValue() : GETOPT_parse3_intern(rv, xsink);
}
