/* -*- mode: c++; indent-tabs-mode: nil -*- */
/** @file QC_Namespace.cpp Namespace namespace definition */
/*
    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_reflection.h>
#include <QC_Namespace.h>
#include <QC_Class.h>
#include <QC_Type.h>
#include "QC_AbstractReflectionFunction.h"
#include "QC_Function.h"
#include "QC_FunctionVariant.h"
#include "QC_GlobalVar.h"
#include "QC_TypedHash.h"
#include "QC_Constant.h"

QoreReflectionNamespace::QoreReflectionNamespace(ExceptionSink* xsink, const QoreString& name, QoreProgram* pgm)
    : AbstractReflectionObject(pgm) {
    ns = pgm->findNamespace(name);

    if (!ns) {
        xsink->raiseException("UNKNOWN-NAMESPACE", "cannot resolve namespace '%s'", name.c_str());
        return;
    }
}

QoreReflectionNamespace::QoreReflectionNamespace(QoreProgram* pgm, const QoreNamespace* ns) : AbstractReflectionObject(pgm), ns(ns) {
}

static QoreValue find_all_namespaces(const QoreString& pattern, int re_opts, QoreProgram* pgm, ExceptionSink* xsink) {
    ns_vec_t ns_vec = pgm->findAllNamespacesRegex(pattern, re_opts, xsink);
    if (*xsink) {
        return QoreValue();
    }

    ReferenceHolder<QoreListNode> l(new QoreListNode(QC_NAMESPACE->getTypeInfo()), xsink);
    for (auto& i : ns_vec) {
        l->push(new QoreObject(QC_NAMESPACE, pgm, new QoreReflectionNamespace(pgm, i)), xsink);
    }

    return l.release();
}

//! This namespace provides information about %Qore namespaces
/** @since %Qore 0.9
 */
qclass Namespace [ns=Qore::Reflection; arg=QoreReflectionNamespace* n; dom=REFLECTION];

//! Creates the object from the name name or namespace-justified path as a string
/** @par Example:
    @code{.py}
Namespace ns("Qore::Thread");
    @endcode

    @param path the namespace name or namespace-justified path as a string; the root namespace is returned with an empty string or \c "::" argument

    @throw UNKNOWN-NAMESPACE cannot find a namespace with the given name or path
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string argument is not in the @ref default_encoding "default character encoding" and an error occurs during encoding conversion

    @see forName()
 */
Namespace::constructor(string path) {
    TempEncodingHelper npath(path, QCS_DEFAULT, xsink);
    if (*xsink) {
        return;
    }

    ReferenceHolder<QoreReflectionNamespace> ns(new QoreReflectionNamespace(xsink, **npath), xsink);
    if (*xsink) {
        return;
    }

    self->setPrivate(CID_NAMESPACE, ns.release());
}

//! returns the namespace's name
/** @par Example:
    @code{.py}
string name = ns.getName();
    @endcode

    @return the namespace's name

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed

    @see getPathName()
 */
string Namespace::getName() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    return new QoreStringNode(n->ns->getName());
}

//! Returns the module name providing the namespace or @ref nothing if the namespace was not provided by a module
/** @par Example:
    @code{.py}
*string str = ns.getModuleName();
    @endcode

    @return the module name providing the namespace or @ref nothing if the namespace was not provided by a module

    @since %Qore 0.9.5
*/
*string Namespace::getModuleName() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    const char* mod = n->ns->getModuleName();
    return mod ? new QoreStringNode(mod) : QoreValue();
}

//! returns the namespace's full namespace-justified path name
/** @par Example:
    @code{.py}
string name = n.getPathName();
    @endcode

    @return the namespace's full namespace-justified path name

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed

    @see getName()
 */
string Namespace::getPathName() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    return new QoreStringNode(n->ns->getPath());
}

//! Returns @ref True if the Namespace object passed as an argument is equal to the current object; @ref False if not
/** @par Example:
    @code{.py}
bool b = n1.isEqual(n2);
    @endcode

    @param ns the namespace to check with the current object for equality

    @return @ref True if the Namespace object passed as an argument is equal to the current object; @ref False if not

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
bool Namespace::isEqual(Namespace[QoreReflectionNamespace] ns) [flags=RET_VALUE_ONLY] {
    ReferenceHolder<QoreReflectionNamespace> holder(ns, xsink);

    // get read access to program object
    QoreExternalProgramContextHelper tnh1(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // get read access to program object
    QoreExternalProgramContextHelper tnh2(xsink, ns->pgm);
    if (*xsink) {
        return QoreValue();
    }

    return ns->ns == n->ns;
}

//! returns a bitfield of @ref modifier_constants "modifiers" describing the namespace
/** @par Example:
    @code{.py}
int i = n.getModifiers();
    @endcode

    @return a bitfield of @ref modifier_constants "modifiers" describing the namespace; possible values are:
    - @ref Qore::Reflection::MC_PUBLIC "MC_PUBLIC": the namespace has the @ref mod_public "module public" flag set

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed

    @see @ref getModifierList()
*/
int Namespace::getModifiers() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    int64 rc = 0;
    if (n->ns->isModulePublic()) {
        rc |= MC_PUBLIC;
    }

    return rc;
}

//! returns a list of strings of modifiers describing the namespace
/** @par Example:
    @code{.py}
list<string> l = n.getModifierList();
    @endcode

    @return list of strings of modifiers describing the namespace; possible values are:
    - \c "public": the namespace has the @ref mod_public "module public" flag set

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed

    @see @ref getModifiers()
*/
list<string> Namespace::getModifierList() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    ReferenceHolder<QoreListNode> rv(new QoreListNode(stringTypeInfo), xsink);
    if (n->ns->isModulePublic()) {
        rv->push(new QoreStringNode("public"), xsink);
    }

    return rv.release();
}

//! returns @ref True if the namespace is a builtin namespace
/** @par Example:
    @code{.py}
bool b = n.isBuiltin();
    @endcode

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
bool Namespace::isBuiltin() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    return n->ns->isBuiltin();
}

//! returns @ref True if the namespace is a user namespace
/** @par Example:
    @code{.py}
bool b = n.isUser();
    @endcode

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
bool Namespace::isUser() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    return !n->ns->isBuiltin();
}

//! returns @ref True if the namespace has the @ref mod_public "module public" flag set
/** @par Example:
    @code{.py}
bool b = n.isModulePublic();
    @endcode

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
bool Namespace::isModulePublic() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    return n->ns->isModulePublic();
}

//! returns @ref True if the namespace has been imported from another @ref Qore::Program "Program" object
/** @par Example:
    @code{.py}
bool b = n.isImported();
    @endcode

    @return @ref True if the namespace has been imported from another @ref Qore::Program "Program" object

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
bool Namespace::isImported() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    return n->ns->isImported();
}

//! returns @ref True if the namespace is the root namespace
/** @par Example:
    @code{.py}
bool b = n.isRoot();
    @endcode

    @return @ref True if the namespace is the root namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
bool Namespace::isRoot() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    return n->ns->isRoot();
}

//! Returns the parent namespace, if any (the root namespace has no parent); if the %Namespace has no parent, then @ref nothing is returned
/** @par Example:
    @code{.py}
*Namespace parent = n.getParentNamespace();
    @endcode

    @return the parent namespace, if any (the root namespace has no parent); if the %Namespace has no parent, then @ref nothing is returned

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
*Namespace Namespace::getParentNamespace() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    const QoreNamespace* parent = n->ns->getParent();
    if (parent) {
        return new QoreObject(QC_NAMESPACE, n->pgm, new QoreReflectionNamespace(n->pgm, parent));
    }
    return QoreValue();
}

//! Returns the given constant or throws an exception if the constant is not defined
/** @par Example:
    @code{.py}
Constant con = n.getConstant(name);
    @endcode

    @return given constant or throws an exception if the constant is not defined

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
    @throw UNKNOWN-CONSTANT thrown if the constant cannot be found
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string argument is not in the @ref default_encoding "default character encoding" and an error occurs during encoding conversion
*/
Constant Namespace::getConstant(string name) [flags=RET_VALUE_ONLY] {
    TempEncodingHelper tname(name, QCS_DEFAULT, xsink);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    const QoreExternalConstant* ce = n->ns->findLocalConstant(tname->c_str());
    if (!ce) {
        xsink->raiseException("UNKNOWN-CONSTANT", "constant '%s' is not defined in namespace '%s'", tname->c_str(), n->ns->getName());
        return QoreValue();
    }

    return new QoreObject(QC_CONSTANT, n->pgm, new QoreReflectionNamespaceConstant(n->pgm, ce, n->ns));
}

//! Returns a list of all declared constants in the namespace
/** @par Example:
    @code{.py}
list<Constant> l = n.getConstants();
    @endcode

    @return a list of all declared constants in the namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
list<Constant> Namespace::getConstants() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    ReferenceHolder<QoreListNode> l(new QoreListNode(QC_CONSTANT->getTypeInfo()), xsink);

    QoreNamespaceConstantIterator i(*n->ns);
    while (i.next()) {
        l->push(new QoreObject(QC_CONSTANT, n->pgm, new QoreReflectionNamespaceConstant(n->pgm, &i.get(), n->ns)), xsink);
    }

    return l.release();
}

//! Returns the given direct child namespace or throws an exception if the namespace is not defined
/** @par Example:
    @code{.py}
Namespace con = n.getNamespace(name);
    @endcode

    @return given namespace or throws an exception if the namespace is not defined

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
    @throw UNKNOWN-NAMESPACE thrown if the namespace cannot be found
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string argument is not in the @ref default_encoding "default character encoding" and an error occurs during encoding conversion
*/
Namespace Namespace::getNamespace(string name) [flags=RET_VALUE_ONLY] {
    TempEncodingHelper tname(name, QCS_DEFAULT, xsink);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    const QoreNamespace* rv = n->ns->findLocalNamespace(tname->c_str());
    if (!rv) {
        xsink->raiseException("UNKNOWN-NAMESPACE", "namespace '%s' is not defined in namespace '%s'", tname->c_str(), n->ns->getName());
        return QoreValue();
    }

    return new QoreObject(QC_NAMESPACE, n->pgm, new QoreReflectionNamespace(n->pgm, rv));
}

//! Returns a list of all declared namespaces in the namespace
/** @par Example:
    @code{.py}
list<Namespace> l = n.getNamespaces();
    @endcode

    @return a list of all declared namespaces in the namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
list<Namespace> Namespace::getNamespaces() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    ReferenceHolder<QoreListNode> l(new QoreListNode(QC_NAMESPACE->getTypeInfo()), xsink);
    QoreNamespaceNamespaceIterator i(*n->ns);
    while (i.next()) {
        l->push(new QoreObject(QC_NAMESPACE, n->pgm, new QoreReflectionNamespace(n->pgm, &i.get())), xsink);
    }

    return l.release();
}

//! Returns the given direct child class declared in this namespace or throws an exception if the class is not defined
/** @par Example:
    @code{.py}
Class cls = n.getClass(name);
    @endcode

    @return given class or throws an exception if the class is not defined in this namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
    @throw UNKNOWN-CLASS thrown if the class cannot be found
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string argument is not in the @ref default_encoding "default character encoding" and an error occurs during encoding conversion
*/
Class Namespace::getClass(string name) [flags=RET_VALUE_ONLY] {
    TempEncodingHelper tname(name, QCS_DEFAULT, xsink);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid

    const QoreClass* rv = n->ns->findLocalClass(tname->c_str());
    if (!rv) {
        xsink->raiseException("UNKNOWN-CLASS", "class '%s' is not defined in namespace '%s'", tname->c_str(), n->ns->getName());
        return QoreValue();
    }

    return new QoreObject(QC_CLASS, n->pgm, new QoreReflectionClass(n->pgm, rv));
}

//! Returns a list of all declared classes in the namespace
/** @par Example:
    @code{.py}
list<Class> l = n.getClasses();
    @endcode

    @return a list of all declared classes in the namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
list<Class> Namespace::getClasses() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    ReferenceHolder<QoreListNode> l(new QoreListNode(QC_CLASS->getTypeInfo()), xsink);

    QoreNamespaceClassIterator i(*n->ns);
    while (i.next()) {
        l->push(new QoreObject(QC_CLASS, n->pgm, new QoreReflectionClass(n->pgm, &i.get())), xsink);
    }

    return l.release();
}

//! Returns the given direct child function declared in this namespace or throws an exception if the function is not defined
/** @par Example:
    @code{.py}
Function f = n.getFunction(name);
    @endcode

    @param name the name of the function to find

    @return given function or throws an exception if the function is not defined in this namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
    @throw UNKNOWN-FUNCTION thrown if the function cannot be found
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string argument is not in the @ref default_encoding "default character encoding" and an error occurs during encoding conversion
*/
Function Namespace::getFunction(string name) [flags=RET_VALUE_ONLY] {
    TempEncodingHelper tname(name, QCS_DEFAULT, xsink);
    if (*xsink) {
        return QoreValue();
    }

    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    const QoreExternalFunction* rv = n->ns->findLocalFunction(tname->c_str());
    if (!rv) {
        xsink->raiseException("UNKNOWN-FUNCTION", "function '%s' is not defined in namespace '%s'", tname->c_str(), n->ns->getName());
        return QoreValue();
    }

    return new QoreObject(QC_FUNCTION, n->pgm, new QoreReflectionFunction(n->pgm, rv));
}

//! Returns a list of all declared functions in the namespace
/** @par Example:
    @code{.py}
list<Function> l = n.getFunctions();
    @endcode

    @return a list of all declared functions in the namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
list<Function> Namespace::getFunctions() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    ReferenceHolder<QoreListNode> l(new QoreListNode(QC_FUNCTION->getTypeInfo()), xsink);

    QoreNamespaceFunctionIterator i(*n->ns);
    while (i.next()) {
        l->push(new QoreObject(QC_FUNCTION, n->pgm, new QoreReflectionFunction(n->pgm, &i.get())), xsink);
    }

    return l.release();
}

//! Returns the given direct child function variant declared in this namespace or throws an exception if the function variant is not defined
/** @par Example:
    @code{.py}
FunctionVariant v = n.getVariant(name, IntType, StringType);
    @endcode

    @param name the name of the function variant to find
    @param ... type arguments for the variant; types must be compatible for the search to be successful

    @return given function variant or throws an exception if the function variant is not defined in this namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
    @throw UNKNOWN-FUNCTION thrown if the function cannot be found
    @throw VARIANT-MATCH-ERROR thrown if the function variant cannot be matched to the given arguments
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string argument is not in the @ref default_encoding "default character encoding" and an error occurs during encoding conversion

    @see
    - getVariantArgs()
    - getExactVariant()
    - getExactVariantArgs()
*/
FunctionVariant Namespace::getVariant(string name, ...) [flags=RET_VALUE_ONLY] {
    TempEncodingHelper tname(name, QCS_DEFAULT, xsink);
    if (*xsink) {
        return QoreValue();
    }

    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    const QoreExternalFunction* func = n->ns->findLocalFunction(tname->c_str());
    if (!func) {
        xsink->raiseException("UNKNOWN-FUNCTION", "function '%s' is not defined in namespace '%s'", tname->c_str(), n->ns->getName());
        return QoreValue();
    }

    return find_function_variant(n->pgm, func, args, 1, MT_None, xsink);
}

//! Returns the given direct child function variant declared in this namespace or throws an exception if the function variant is not defined
/** @par Example:
    @code{.py}
FunctionVariant v = n.getVariantArgs(name, (IntType, StringType));
    @endcode

    @param name the name of the function variant to find
    @param argv type arguments for the variant; types must be compatible for the search to be successful

    @return given function variant or throws an exception if the function variant is not defined in this namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
    @throw UNKNOWN-FUNCTION thrown if the function cannot be found
    @throw VARIANT-MATCH-ERROR thrown if the function variant cannot be matched to the given arguments
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string argument is not in the @ref default_encoding "default character encoding" and an error occurs during encoding conversion

    @see
    - getVariant()
    - getExactVariant()
    - getExactVariantArgs()
*/
FunctionVariant Namespace::getVariantArgs(string name, softlist<Type> argv) [flags=RET_VALUE_ONLY] {
    TempEncodingHelper tname(name, QCS_DEFAULT, xsink);
    if (*xsink) {
        return QoreValue();
    }

    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    const QoreExternalFunction* func = n->ns->findLocalFunction(tname->c_str());
    if (!func) {
        xsink->raiseException("UNKNOWN-FUNCTION", "function '%s' is not defined in namespace '%s'", tname->c_str(), n->ns->getName());
        return QoreValue();
    }

    return find_function_variant(n->pgm, func, argv, 0, MT_None, xsink);
}

//! Returns a list of all declared function variants in the namespace
/** @par Example:
    @code{.py}
list<FunctionVariant> l = n.getVariants();
    @endcode

    @return a list of all declared function variants in the namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
list<FunctionVariant> Namespace::getVariants() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    ReferenceHolder<QoreListNode> l(new QoreListNode(QC_FUNCTIONVARIANT->getTypeInfo()), xsink);

    QoreNamespaceFunctionIterator i(*n->ns);
    while (i.next()) {
        const QoreExternalFunction& f = i.get();
        append_variant_objects(**l, n->pgm, &f, MT_None, xsink);
        if (*xsink) {
            return QoreValue();
        }
    }

    return l.release();
}

//! Returns the given direct child global variable declared in this namespace or throws an exception if the global variable is not defined
/** @par Example:
    @code{.py}
GlobalVar cls = n.getGlobalVar(name);
    @endcode

    @return given global variable or throws an exception if the global variable is not defined in this namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
    @throw UNKNOWN-GLOBAL-VAR thrown if the global variable cannot be found
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string argument is not in the @ref default_encoding "default character encoding" and an error occurs during encoding conversion
*/
GlobalVar Namespace::getGlobalVar(string name) [flags=RET_VALUE_ONLY] {
    TempEncodingHelper tname(name, QCS_DEFAULT, xsink);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    const QoreExternalGlobalVar* rv = n->ns->findLocalGlobalVar(tname->c_str());
    if (!rv) {
        xsink->raiseException("UNKNOWN-GLOBAL-VAR", "global variable '%s' is not defined in namespace '%s'", tname->c_str(), n->ns->getName());
        return QoreValue();
    }

    return new QoreObject(QC_GLOBALVAR, n->pgm, new QoreReflectionGlobalVar(n->pgm, rv, n->ns));
}

//! Returns a list of all declared global variables in the namespace
/** @par Example:
    @code{.py}
list<GlobalVar> l = n.getGlobalVars();
    @endcode

    @return a list of all declared global variables in the namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
list<GlobalVar> Namespace::getGlobalVars() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    ReferenceHolder<QoreListNode> l(new QoreListNode(QC_GLOBALVAR->getTypeInfo()), xsink);
    QoreNamespaceGlobalVarIterator i(*n->ns);
    while (i.next()) {
        l->push(new QoreObject(QC_GLOBALVAR, n->pgm, new QoreReflectionGlobalVar(n->pgm, &i.get(), n->ns)), xsink);
    }

    return l.release();
}

//! Returns the given direct child @ref hashdecl "typed hash (hashdecl)" declared in this namespace or throws an exception if the global variable is not defined
/** @par Example:
    @code{.py}
TypedHash th = n.getTypedHash(name);
    @endcode

    @return given @ref hashdecl "typed hash (hashdecl)" or throws an exception if the @ref hashdecl "typed hash (hashdecl)" is not defined in this namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
    @throw UNKNOWN-TYPED-HASH thrown if the @ref hashdecl "typed hash (hashdecl)" cannot be found
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string argument is not in the @ref default_encoding "default character encoding" and an error occurs during encoding conversion
*/
TypedHash Namespace::getTypedHash(string name) [flags=RET_VALUE_ONLY] {
    TempEncodingHelper tname(name, QCS_DEFAULT, xsink);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    const TypedHashDecl* rv = n->ns->findLocalTypedHash(tname->c_str());
    if (!rv) {
        xsink->raiseException("UNKNOWN-TYPED-HASH", "typed hash (hashdecl) '%s' is not defined in namespace '%s'", tname->c_str(), n->ns->getName());
        return QoreValue();
    }

    return new QoreObject(QC_TYPEDHASH, n->pgm, new QoreReflectionTypedHash(n->pgm, rv, n->ns));
}

//! Returns a list of all declared @ref hashdecl "typed hashes (hashdecls)" in the namespace
/** @par Example:
    @code{.py}
list<TypedHash> l = n.getTypedHashes();
    @endcode

    @return a list of all declared @ref hashdecl "typed hashes (hashdecls)" in the namespace

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the namespace has been destroyed
*/
list<TypedHash> Namespace::getTypedHashes() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, n->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the namespace ptr must be valid
    ReferenceHolder<QoreListNode> l(new QoreListNode(QC_TYPEDHASH->getTypeInfo()), xsink);
    QoreNamespaceTypedHashIterator i(*n->ns);
    while (i.next()) {
        l->push(new QoreObject(QC_TYPEDHASH, n->pgm, new QoreReflectionTypedHash(n->pgm, &i.get(), n->ns)), xsink);
    }

    return l.release();
}

//! Returns a %Namespace object from the namespace name or namespace-justified path as a string
/** @par Example:
    @code{.py}
Namespace n = Namespace::forName("Thread::Mutex");
    @endcode

    @param path the namespace name or namespace-justified path as a string; the root namespace is returned with an empty string or \c "::" argument

    @return a namespace object corresponding to the name or namespace-justified path provided as an argument

    @throw UNKNOWN-NAMESPACE cannot find a namespace with the given name or path
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string argument is not in the @ref default_encoding "default character encoding" and an error occurs during encoding conversion
 */
static Namespace Namespace::forName(string path) [flags=RET_VALUE_ONLY] {
    TempEncodingHelper npath(path, QCS_DEFAULT, xsink);
    if (*xsink) {
        return QoreValue();
    }

    ReferenceHolder<QoreReflectionNamespace> n(new QoreReflectionNamespace(xsink, **npath), xsink);
    if (*xsink) {
        return QoreValue();
    }

    return new QoreObject(QC_NAMESPACE, getProgram(), n.release());
}

//! Returns a %Namespace object from the namespace name or namespace-justified path as a string
/** @par Example:
    @code{.py}
Namespace n = Namespace::forName(pgm, "Thread::Mutex");
    @endcode

    @param pgm the Program object to search
    @param path the namespace name or namespace-justified path as a string; the root namespace is returned with an empty string or \c "::" argument

    @return a namespace object corresponding to the name or namespace-justified path provided as an argument

    @throw UNKNOWN-NAMESPACE cannot find a namespace with the given name or path
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string argument is not in the @ref default_encoding "default character encoding" and an error occurs during encoding conversion
 */
static Namespace Namespace::forName(Program[QoreProgram] pgm, string path) [flags=RET_VALUE_ONLY] {
    ReferenceHolder<QoreProgram> pgm_holder(pgm, xsink);
    TempEncodingHelper npath(path, QCS_DEFAULT, xsink);
    if (*xsink) {
        return QoreValue();
    }

    ReferenceHolder<QoreReflectionNamespace> n(new QoreReflectionNamespace(xsink, **npath, pgm), xsink);
    if (*xsink) {
        return QoreValue();
    }

    return new QoreObject(QC_NAMESPACE, pgm, n.release());
}

//! Returns a list of namespaces matching the regular expression pattern argument or an empty list if no namespaces match
/** @par Example:
    @code{.py}
# return all accessible namespaces that start with the letter "g"
list<Namespace> l = Namespace::findAllRegex("^g");
    @endcode

    @param pattern the regular expression pattern to match
    @param re_opts see @ref regex_constants for possible values

    @return a list of namespaces matching the regular expression pattern argument or an empty list if no namespaces match
*/
static list<Namespace> Namespace::findAllRegex(string pattern, *int re_opts) [flags=RET_VALUE_ONLY] {
    return find_all_namespaces(*pattern, re_opts, getProgram(), xsink);
}

//! Returns a list of namespaces matching the regular expression pattern argument or an empty list if no namespaces match
/** @par Example:
    @code{.py}
# return all accessible namespaces that start with the letter "g"
list<Namespace> l = Namespace::findAllRegex(pgm, "^g");
    @endcode

    @param pgm the Program object to search
    @param pattern the regular expression pattern to match
    @param re_opts see @ref regex_constants for possible values

    @return a list of namespaces matching the regular expression pattern argument or an empty list if no namespaces match
*/
static list<Namespace> Namespace::findAllRegex(Program[QoreProgram] pgm, string pattern, *int re_opts) [flags=RET_VALUE_ONLY] {
    ReferenceHolder<QoreProgram> pgm_holder(pgm, xsink);
    return find_all_namespaces(*pattern, re_opts, pgm, xsink);
}
