/* $Id: VDScriptInterp.cpp $ */
/** @file
 * VBox HDD container test utility - scripting engine, interpreter.
 */

/*
 * Copyright (C) 2013-2023 Oracle and/or its affiliates.
 *
 * This file is part of VirtualBox base platform packages, as
 * available from https://www.virtualbox.org.
 *
 * 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, in version 3 of the
 * License.
 *
 * 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 <https://www.gnu.org/licenses>.
 *
 * SPDX-License-Identifier: GPL-3.0-only
 */

#define LOGGROUP LOGGROUP_DEFAULT
#include <iprt/assert.h>
#include <iprt/err.h>
#include <iprt/list.h>
#include <iprt/mem.h>
#include <iprt/stream.h>
#include <iprt/string.h>

#include <VBox/log.h>

#include "VDScriptAst.h"
#include "VDScriptStack.h"
#include "VDScriptInternal.h"

/**
 * Interpreter variable.
 */
typedef struct VDSCRIPTINTERPVAR
{
    /** String space core. */
    RTSTRSPACECORE              Core;
    /** Value. */
    VDSCRIPTARG                 Value;
} VDSCRIPTINTERPVAR;
/** Pointer to an interpreter variable. */
typedef VDSCRIPTINTERPVAR *PVDSCRIPTINTERPVAR;

/**
 * Block scope.
 */
typedef struct VDSCRIPTINTERPSCOPE
{
    /** Pointer to the enclosing scope if available. */
    struct VDSCRIPTINTERPSCOPE *pParent;
    /** String space of declared variables in this scope. */
    RTSTRSPACE                  hStrSpaceVar;
} VDSCRIPTINTERPSCOPE;
/** Pointer to a scope block. */
typedef VDSCRIPTINTERPSCOPE *PVDSCRIPTINTERPSCOPE;

/**
 * Function call.
 */
typedef struct VDSCRIPTINTERPFNCALL
{
    /** Pointer to the caller of this function. */
    struct VDSCRIPTINTERPFNCALL *pCaller;
    /** Root scope of this function. */
    VDSCRIPTINTERPSCOPE          ScopeRoot;
    /** Current scope in this function. */
    PVDSCRIPTINTERPSCOPE         pScopeCurr;
} VDSCRIPTINTERPFNCALL;
/** Pointer to a function call. */
typedef VDSCRIPTINTERPFNCALL *PVDSCRIPTINTERPFNCALL;

/**
 * Interpreter context.
 */
typedef struct VDSCRIPTINTERPCTX
{
    /** Pointer to the script context. */
    PVDSCRIPTCTXINT             pScriptCtx;
    /** Current function call entry. */
    PVDSCRIPTINTERPFNCALL       pFnCallCurr;
    /** Stack of calculated values. */
    VDSCRIPTSTACK               StackValues;
    /** Evaluation control stack. */
    VDSCRIPTSTACK               StackCtrl;
} VDSCRIPTINTERPCTX;
/** Pointer to an interpreter context. */
typedef VDSCRIPTINTERPCTX *PVDSCRIPTINTERPCTX;

/**
 * Interpreter control type.
 */
typedef enum VDSCRIPTINTERPCTRLTYPE
{
    VDSCRIPTINTERPCTRLTYPE_INVALID = 0,
    /** Function call to evaluate now, all values are computed
     * and are stored on the value stack.
     */
    VDSCRIPTINTERPCTRLTYPE_FN_CALL,
    /** Cleanup the function call, deleting the scope and restoring the previous one. */
    VDSCRIPTINTERPCTRLTYPE_FN_CALL_CLEANUP,
    /** If statement to evaluate now, the guard is on the stack. */
    VDSCRIPTINTERPCTRLTYPE_IF,
    /** While or for statement. */
    VDSCRIPTINTERPCTRLTYPE_LOOP,
    /** switch statement. */
    VDSCRIPTINTERPCTRLTYPE_SWITCH,
    /** Compound statement. */
    VDSCRIPTINTERPCTRLTYPE_COMPOUND,
    /** 32bit blowup. */
    VDSCRIPTINTERPCTRLTYPE_32BIT_HACK = 0x7fffffff
} VDSCRIPTINTERPCTRLTYPE;
/** Pointer to a control type. */
typedef VDSCRIPTINTERPCTRLTYPE *PVDSCRIPTINTERPCTRLTYPE;

/**
 * Interpreter stack control entry.
 */
typedef struct VDSCRIPTINTERPCTRL
{
    /** Flag whether this entry points to an AST node to evaluate. */
    bool                           fEvalAst;
    /** Flag dependent data. */
    union
    {
        /** Pointer to the AST node to interprete. */
        PVDSCRIPTASTCORE           pAstNode;
        /** Interpreter control structure. */
        struct
        {
            /** Type of control. */
            VDSCRIPTINTERPCTRLTYPE enmCtrlType;
            /** Function call data. */
            struct
            {
                /** Function to call. */
                PVDSCRIPTFN        pFn;
            } FnCall;
            /** Compound statement. */
            struct
            {
                /** The compound statement node. */
                PVDSCRIPTASTSTMT   pStmtCompound;
                /** Current statement evaluated. */
                PVDSCRIPTASTSTMT   pStmtCurr;
            } Compound;
            /** Pointer to an AST node. */
            PVDSCRIPTASTCORE       pAstNode;
        } Ctrl;
    };
} VDSCRIPTINTERPCTRL;
/** Pointer to an exec stack control entry. */
typedef VDSCRIPTINTERPCTRL *PVDSCRIPTINTERPCTRL;

/**
 * Record an error while interpreting.
 *
 * @returns VBox status code passed.
 * @param   pThis      The interpreter context.
 * @param   rc         The status code to record.
 * @param   RT_SRC_POS Position in the source code.
 * @param   pszFmt     Format string.
 */
static int vdScriptInterpreterError(PVDSCRIPTINTERPCTX pThis, int rc, RT_SRC_POS_DECL, const char *pszFmt, ...)
{
    RT_NOREF1(pThis); RT_SRC_POS_NOREF();
    va_list va;
    va_start(va, pszFmt);
    RTPrintfV(pszFmt, va);
    va_end(va);
    return rc;
}

/**
 * Pops the topmost value from the value stack.
 *
 * @param   pThis      The interpreter context.
 * @param   pVal       Where to store the value.
 */
DECLINLINE(void) vdScriptInterpreterPopValue(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTARG pVal)
{
    PVDSCRIPTARG pValStack = (PVDSCRIPTARG)vdScriptStackGetUsed(&pThis->StackValues);
    if (!pValStack)
    {
        RT_ZERO(*pVal);
        AssertPtrReturnVoid(pValStack);
    }

    *pVal = *pValStack;
    vdScriptStackPop(&pThis->StackValues);
}

/**
 * Pushes a given value onto the value stack.
 */
DECLINLINE(int) vdScriptInterpreterPushValue(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTARG pVal)
{
    PVDSCRIPTARG pValStack = (PVDSCRIPTARG)vdScriptStackGetUnused(&pThis->StackValues);
    if (!pValStack)
        return vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory pushing a value on the value stack");

    *pValStack = *pVal;
    vdScriptStackPush(&pThis->StackValues);
    return VINF_SUCCESS;
}

/**
 * Pushes an AST node onto the control stack.
 *
 * @returns VBox status code.
 * @param   pThis          The interpreter context.
 * @param   enmCtrlType    The control entry type.
 */
DECLINLINE(int) vdScriptInterpreterPushAstEntry(PVDSCRIPTINTERPCTX pThis,
                                                PVDSCRIPTASTCORE pAstNode)
{
    PVDSCRIPTINTERPCTRL pCtrl = NULL;

    pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);

    if (pCtrl)
    {
        pCtrl->fEvalAst = true;
        pCtrl->pAstNode = pAstNode;
        vdScriptStackPush(&pThis->StackCtrl);
        return VINF_SUCCESS;
    }

    return vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
}

/**
 * Pushes a control entry without additional data onto the stack.
 *
 * @returns VBox status code.
 * @param   pThis          The interpreter context.
 * @param   enmCtrlType    The control entry type.
 */
DECLINLINE(int) vdScriptInterpreterPushNonDataCtrlEntry(PVDSCRIPTINTERPCTX pThis,
                                                        VDSCRIPTINTERPCTRLTYPE enmCtrlType)
{
    PVDSCRIPTINTERPCTRL pCtrl = NULL;

    pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
    if (pCtrl)
    {
        pCtrl->fEvalAst = false;
        pCtrl->Ctrl.enmCtrlType = enmCtrlType;
        vdScriptStackPush(&pThis->StackCtrl);
        return VINF_SUCCESS;
    }

    return vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
}

/**
 * Pushes a compound statement control entry onto the stack.
 *
 * @returns VBox status code.
 * @param   pThis          The interpreter context.
 * @param   pStmtFirst     The first statement of the compound statement
 */
DECLINLINE(int) vdScriptInterpreterPushCompoundCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
{
    PVDSCRIPTINTERPCTRL pCtrl = NULL;

    pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
    if (pCtrl)
    {
        pCtrl->fEvalAst = false;
        pCtrl->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_COMPOUND;
        pCtrl->Ctrl.Compound.pStmtCompound = pStmt;
        pCtrl->Ctrl.Compound.pStmtCurr = RTListGetFirst(&pStmt->Compound.ListStmts, VDSCRIPTASTSTMT, Core.ListNode);
        vdScriptStackPush(&pThis->StackCtrl);
        return VINF_SUCCESS;
    }

    return vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
}

/**
 * Pushes a while statement control entry onto the stack.
 *
 * @returns VBox status code.
 * @param   pThis          The interpreter context.
 * @param   pStmt          The while statement.
 */
DECLINLINE(int) vdScriptInterpreterPushWhileCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
{
    int rc = VINF_SUCCESS;
    PVDSCRIPTINTERPCTRL pCtrl = NULL;

    pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
    if (pCtrl)
    {
        pCtrl->fEvalAst = false;
        pCtrl->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_LOOP;
        pCtrl->Ctrl.pAstNode    = &pStmt->Core;
        vdScriptStackPush(&pThis->StackCtrl);

        rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->While.pCond->Core);
        if (   RT_SUCCESS(rc)
            && pStmt->While.fDoWhile)
        {
            /* Push the statement to execute for do ... while loops because they run at least once. */
            rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->While.pStmt->Core);
            if (RT_FAILURE(rc))
            {
                /* Cleanup while control statement and AST node. */
                vdScriptStackPop(&pThis->StackCtrl);
                vdScriptStackPop(&pThis->StackCtrl);
            }
        }
        else if (RT_FAILURE(rc))
            vdScriptStackPop(&pThis->StackCtrl); /* Cleanup the while control statement. */
    }
    else
        rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");

    return rc;
}

/**
 * Pushes an if statement control entry onto the stack.
 *
 * @returns VBox status code.
 * @param   pThis          The interpreter context.
 * @param   pStmt          The if statement.
 */
static int vdScriptInterpreterPushIfCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
{
    int rc = VINF_SUCCESS;
    PVDSCRIPTINTERPCTRL pCtrl = NULL;

    pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
    if (pCtrl)
    {
        pCtrl->fEvalAst = false;
        pCtrl->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_IF;
        pCtrl->Ctrl.pAstNode    = &pStmt->Core;
        vdScriptStackPush(&pThis->StackCtrl);

        rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->If.pCond->Core);
    }
    else
        rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");

    return rc;
}

/**
 * Pushes a for statement control entry onto the stack.
 *
 * @returns VBox status code.
 * @param   pThis          The interpreter context.
 * @param   pStmt          The while statement.
 */
DECLINLINE(int) vdScriptInterpreterPushForCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
{
    int rc = VINF_SUCCESS;
    PVDSCRIPTINTERPCTRL pCtrl = NULL;

    pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
    if (pCtrl)
    {
        pCtrl->fEvalAst = false;
        pCtrl->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_LOOP;
        pCtrl->Ctrl.pAstNode    = &pStmt->Core;
        vdScriptStackPush(&pThis->StackCtrl);

        /* Push the conditional first and the initializer .*/
        rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->For.pExprCond->Core);
        if (RT_SUCCESS(rc))
        {
            rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->For.pExprStart->Core);
            if (RT_FAILURE(rc))
                vdScriptStackPop(&pThis->StackCtrl);
        }
    }
    else
        rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");

    return rc;
}

/**
 * Destroy variable string space callback.
 */
static DECLCALLBACK(int) vdScriptInterpreterVarSpaceDestroy(PRTSTRSPACECORE pStr, void *pvUser)
{
    RT_NOREF1(pvUser);
    RTMemFree(pStr);
    return VINF_SUCCESS;
}

/**
 * Setsup a new scope in the current function call.
 *
 * @returns VBox status code.
 * @param   pThis          The interpreter context.
 */
static int vdScriptInterpreterScopeCreate(PVDSCRIPTINTERPCTX pThis)
{
    int rc = VINF_SUCCESS;
    PVDSCRIPTINTERPSCOPE pScope = (PVDSCRIPTINTERPSCOPE)RTMemAllocZ(sizeof(VDSCRIPTINTERPSCOPE));
    if (pScope)
    {
        pScope->pParent = pThis->pFnCallCurr->pScopeCurr;
        pScope->hStrSpaceVar = NULL;
        pThis->pFnCallCurr->pScopeCurr = pScope;
    }
    else
        rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory allocating new scope");

    return rc;
}

/**
 * Destroys the current scope.
 *
 * @param   pThis          The interpreter context.
 */
static void vdScriptInterpreterScopeDestroyCurr(PVDSCRIPTINTERPCTX pThis)
{
    AssertMsgReturnVoid(pThis->pFnCallCurr->pScopeCurr != &pThis->pFnCallCurr->ScopeRoot,
                        ("Current scope is root scope of function call\n"));

    PVDSCRIPTINTERPSCOPE pScope = pThis->pFnCallCurr->pScopeCurr;
    pThis->pFnCallCurr->pScopeCurr = pScope->pParent;
    RTStrSpaceDestroy(&pScope->hStrSpaceVar, vdScriptInterpreterVarSpaceDestroy, NULL);
    RTMemFree(pScope);
}

/**
 * Get the content of the given variable identifier from the current or parent scope.
 */
static PVDSCRIPTINTERPVAR vdScriptInterpreterGetVar(PVDSCRIPTINTERPCTX pThis, const char *pszVar)
{
    PVDSCRIPTINTERPSCOPE pScopeCurr = pThis->pFnCallCurr->pScopeCurr;
    PVDSCRIPTINTERPVAR pVar = NULL;

    while (   !pVar
           && pScopeCurr)
    {
        pVar = (PVDSCRIPTINTERPVAR)RTStrSpaceGet(&pScopeCurr->hStrSpaceVar, pszVar);
        if (pVar)
            break;
        pScopeCurr = pScopeCurr->pParent;
    }


    return pVar;
}

/**
 * Evaluate an expression.
 *
 * @returns VBox status code.
 * @param   pThis      The interpreter context.
 * @param   pExpr      The expression to evaluate.
 */
static int vdScriptInterpreterEvaluateExpression(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTEXPR pExpr)
{
    int rc = VINF_SUCCESS;

    switch (pExpr->enmType)
    {
        case VDSCRIPTEXPRTYPE_PRIMARY_NUMCONST:
        {
            /* Push the numerical constant on the value stack. */
            VDSCRIPTARG NumConst;
            NumConst.enmType = VDSCRIPTTYPE_UINT64;
            NumConst.u64     = pExpr->u64;
            rc = vdScriptInterpreterPushValue(pThis, &NumConst);
            break;
        }
        case VDSCRIPTEXPRTYPE_PRIMARY_STRINGCONST:
        {
            /* Push the string literal on the value stack. */
            VDSCRIPTARG StringConst;
            StringConst.enmType = VDSCRIPTTYPE_STRING;
            StringConst.psz     = pExpr->pszStr;
            rc = vdScriptInterpreterPushValue(pThis, &StringConst);
            break;
        }
        case VDSCRIPTEXPRTYPE_PRIMARY_BOOLEAN:
        {
            VDSCRIPTARG BoolConst;
            BoolConst.enmType = VDSCRIPTTYPE_BOOL;
            BoolConst.f       = pExpr->f;
            rc = vdScriptInterpreterPushValue(pThis, &BoolConst);
            break;
        }
        case VDSCRIPTEXPRTYPE_PRIMARY_IDENTIFIER:
        {
            /* Look it up and push the value onto the value stack. */
            PVDSCRIPTINTERPVAR pVar = vdScriptInterpreterGetVar(pThis, pExpr->pIde->aszIde);

            AssertPtrReturn(pVar, VERR_IPE_UNINITIALIZED_STATUS);
            rc = vdScriptInterpreterPushValue(pThis, &pVar->Value);
            break;
        }
        case VDSCRIPTEXPRTYPE_POSTFIX_INCREMENT:
        case VDSCRIPTEXPRTYPE_POSTFIX_DECREMENT:
            AssertMsgFailed(("TODO\n"));
            RT_FALL_THRU();
        case VDSCRIPTEXPRTYPE_POSTFIX_FNCALL:
        {
            PVDSCRIPTFN pFn = (PVDSCRIPTFN)RTStrSpaceGet(&pThis->pScriptCtx->hStrSpaceFn, pExpr->FnCall.pFnIde->pIde->aszIde);
            if (pFn)
            {
                /* Push a function call control entry on the stack. */
                PVDSCRIPTINTERPCTRL pCtrlFn = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
                if (pCtrlFn)
                {
                    pCtrlFn->fEvalAst = false;
                    pCtrlFn->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_FN_CALL;
                    pCtrlFn->Ctrl.FnCall.pFn = pFn;
                    vdScriptStackPush(&pThis->StackCtrl);

                    /* Push parameter expressions on the stack. */
                    PVDSCRIPTASTEXPR pArg = RTListGetFirst(&pExpr->FnCall.ListArgs, VDSCRIPTASTEXPR, Core.ListNode);
                    while (pArg)
                    {
                        rc = vdScriptInterpreterPushAstEntry(pThis, &pArg->Core);
                        if (RT_FAILURE(rc))
                            break;
                        pArg = RTListGetNext(&pExpr->FnCall.ListArgs, pArg, VDSCRIPTASTEXPR, Core.ListNode);
                    }
                }
            }
            else
                AssertMsgFailed(("Invalid program given, unknown function: %s\n", pExpr->FnCall.pFnIde->pIde->aszIde));
            break;
        }
        case VDSCRIPTEXPRTYPE_UNARY_INCREMENT:
        case VDSCRIPTEXPRTYPE_UNARY_DECREMENT:
        case VDSCRIPTEXPRTYPE_UNARY_POSSIGN:
        case VDSCRIPTEXPRTYPE_UNARY_NEGSIGN:
        case VDSCRIPTEXPRTYPE_UNARY_INVERT:
        case VDSCRIPTEXPRTYPE_UNARY_NEGATE:
        case VDSCRIPTEXPRTYPE_MULTIPLICATION:
        case VDSCRIPTEXPRTYPE_DIVISION:
        case VDSCRIPTEXPRTYPE_MODULUS:
        case VDSCRIPTEXPRTYPE_ADDITION:
        case VDSCRIPTEXPRTYPE_SUBTRACTION:
        case VDSCRIPTEXPRTYPE_LSR:
        case VDSCRIPTEXPRTYPE_LSL:
        case VDSCRIPTEXPRTYPE_LOWER:
        case VDSCRIPTEXPRTYPE_HIGHER:
        case VDSCRIPTEXPRTYPE_LOWEREQUAL:
        case VDSCRIPTEXPRTYPE_HIGHEREQUAL:
        case VDSCRIPTEXPRTYPE_EQUAL:
        case VDSCRIPTEXPRTYPE_NOTEQUAL:
        case VDSCRIPTEXPRTYPE_BITWISE_AND:
        case VDSCRIPTEXPRTYPE_BITWISE_XOR:
        case VDSCRIPTEXPRTYPE_BITWISE_OR:
        case VDSCRIPTEXPRTYPE_LOGICAL_AND:
        case VDSCRIPTEXPRTYPE_LOGICAL_OR:
        case VDSCRIPTEXPRTYPE_ASSIGN:
        case VDSCRIPTEXPRTYPE_ASSIGN_MULT:
        case VDSCRIPTEXPRTYPE_ASSIGN_DIV:
        case VDSCRIPTEXPRTYPE_ASSIGN_MOD:
        case VDSCRIPTEXPRTYPE_ASSIGN_ADD:
        case VDSCRIPTEXPRTYPE_ASSIGN_SUB:
        case VDSCRIPTEXPRTYPE_ASSIGN_LSL:
        case VDSCRIPTEXPRTYPE_ASSIGN_LSR:
        case VDSCRIPTEXPRTYPE_ASSIGN_AND:
        case VDSCRIPTEXPRTYPE_ASSIGN_XOR:
        case VDSCRIPTEXPRTYPE_ASSIGN_OR:
        case VDSCRIPTEXPRTYPE_ASSIGNMENT_LIST:
            AssertMsgFailed(("TODO\n"));
            RT_FALL_THRU();
        default:
            AssertMsgFailed(("Invalid expression type: %d\n", pExpr->enmType));
    }
    return rc;
}

/**
 * Evaluate a statement.
 *
 * @returns VBox status code.
 * @param   pThis      The interpreter context.
 * @param   pStmt      The statement to evaluate.
 */
static int vdScriptInterpreterEvaluateStatement(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
{
    int rc = VINF_SUCCESS;

    switch (pStmt->enmStmtType)
    {
        case VDSCRIPTSTMTTYPE_COMPOUND:
        {
            /* Setup new scope. */
            rc = vdScriptInterpreterScopeCreate(pThis);
            if (RT_SUCCESS(rc))
            {
                /** @todo Declarations */
                rc = vdScriptInterpreterPushCompoundCtrlEntry(pThis, pStmt);
            }
            break;
        }
        case VDSCRIPTSTMTTYPE_EXPRESSION:
        {
            rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->pExpr->Core);
            break;
        }
        case VDSCRIPTSTMTTYPE_IF:
        {
            rc = vdScriptInterpreterPushIfCtrlEntry(pThis, pStmt);
            break;
        }
        case VDSCRIPTSTMTTYPE_SWITCH:
            AssertMsgFailed(("TODO\n"));
            break;
        case VDSCRIPTSTMTTYPE_WHILE:
        {
            rc = vdScriptInterpreterPushWhileCtrlEntry(pThis, pStmt);
            break;
        }
        case VDSCRIPTSTMTTYPE_RETURN:
        {
            /* Walk up the control stack until we reach a function cleanup entry. */
            PVDSCRIPTINTERPCTRL pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
            while (   pCtrl
                   && (   pCtrl->fEvalAst
                       || pCtrl->Ctrl.enmCtrlType != VDSCRIPTINTERPCTRLTYPE_FN_CALL_CLEANUP))
            {
                /* Cleanup up any compound statement scope. */
                if (   !pCtrl->fEvalAst
                    && pCtrl->Ctrl.enmCtrlType == VDSCRIPTINTERPCTRLTYPE_COMPOUND)
                    vdScriptInterpreterScopeDestroyCurr(pThis);

                vdScriptStackPop(&pThis->StackCtrl);
                pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
            }
            AssertMsg(RT_VALID_PTR(pCtrl), ("Incorrect program, return outside of function\n"));
            break;
        }
        case VDSCRIPTSTMTTYPE_FOR:
        {
            rc = vdScriptInterpreterPushForCtrlEntry(pThis, pStmt);
            break;
        }
        case VDSCRIPTSTMTTYPE_CONTINUE:
        {
            /* Remove everything up to a loop control entry. */
            PVDSCRIPTINTERPCTRL pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
            while (   pCtrl
                   && (   pCtrl->fEvalAst
                       || pCtrl->Ctrl.enmCtrlType != VDSCRIPTINTERPCTRLTYPE_LOOP))
            {
                /* Cleanup up any compound statement scope. */
                if (   !pCtrl->fEvalAst
                    && pCtrl->Ctrl.enmCtrlType == VDSCRIPTINTERPCTRLTYPE_COMPOUND)
                    vdScriptInterpreterScopeDestroyCurr(pThis);

                vdScriptStackPop(&pThis->StackCtrl);
                pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
            }
            AssertMsg(RT_VALID_PTR(pCtrl), ("Incorrect program, continue outside of loop\n"));

            /* Put the conditionals for while and for loops onto the control stack again. */
            PVDSCRIPTASTSTMT pLoopStmt = (PVDSCRIPTASTSTMT)pCtrl->Ctrl.pAstNode;

            AssertMsg(   pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_WHILE
                      || pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_FOR,
                      ("Invalid statement type, must be for or while loop\n"));

            if (pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_FOR)
                rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->For.pExprCond->Core);
            else if (!pLoopStmt->While.fDoWhile)
                rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->While.pCond->Core);
            break;
        }
        case VDSCRIPTSTMTTYPE_BREAK:
        {
            /* Remove everything including the loop control statement. */
            PVDSCRIPTINTERPCTRL pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
            while (   pCtrl
                   && (   pCtrl->fEvalAst
                       || pCtrl->Ctrl.enmCtrlType != VDSCRIPTINTERPCTRLTYPE_LOOP))
            {
                /* Cleanup up any compound statement scope. */
                if (   !pCtrl->fEvalAst
                    && pCtrl->Ctrl.enmCtrlType == VDSCRIPTINTERPCTRLTYPE_COMPOUND)
                    vdScriptInterpreterScopeDestroyCurr(pThis);

                vdScriptStackPop(&pThis->StackCtrl);
                pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
            }
            AssertMsg(RT_VALID_PTR(pCtrl), ("Incorrect program, break outside of loop\n"));
            vdScriptStackPop(&pThis->StackCtrl); /* Remove loop control statement. */
            break;
        }
        case VDSCRIPTSTMTTYPE_CASE:
        case VDSCRIPTSTMTTYPE_DEFAULT:
            AssertMsgFailed(("TODO\n"));
            break;
        default:
            AssertMsgFailed(("Invalid statement type: %d\n", pStmt->enmStmtType));
    }

    return rc;
}

/**
 * Evaluates the given AST node.
 *
 * @returns VBox statuse code.
 * @param   pThis      The interpreter context.
 * @param   pAstNode   The AST node to interpret.
 */
static int vdScriptInterpreterEvaluateAst(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTCORE pAstNode)
{
    int rc = VERR_NOT_IMPLEMENTED;

    switch (pAstNode->enmClass)
    {
        case VDSCRIPTASTCLASS_DECLARATION:
        {
            AssertMsgFailed(("TODO\n"));
            break;
        }
        case VDSCRIPTASTCLASS_STATEMENT:
        {
            rc = vdScriptInterpreterEvaluateStatement(pThis, (PVDSCRIPTASTSTMT)pAstNode);
            break;
        }
        case VDSCRIPTASTCLASS_EXPRESSION:
        {
            rc = vdScriptInterpreterEvaluateExpression(pThis, (PVDSCRIPTASTEXPR)pAstNode);
            break;
        }
        /* These should never ever appear here. */
        case VDSCRIPTASTCLASS_IDENTIFIER:
        case VDSCRIPTASTCLASS_FUNCTION:
        case VDSCRIPTASTCLASS_FUNCTIONARG:
        case VDSCRIPTASTCLASS_INVALID:
        default:
            AssertMsgFailed(("Invalid AST node class: %d\n", pAstNode->enmClass));
    }

    return rc;
}

/**
 * Evaluate a function call.
 *
 * @returns VBox status code.
 * @param   pThis      The interpreter context.
 * @param   pFn        The function execute.
 */
static int vdScriptInterpreterFnCall(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTFN pFn)
{
    int rc = VINF_SUCCESS;

    if (!pFn->fExternal)
    {
        PVDSCRIPTASTFN pAstFn = pFn->Type.Internal.pAstFn;

        /* Add function call cleanup marker on the stack first. */
        rc = vdScriptInterpreterPushNonDataCtrlEntry(pThis, VDSCRIPTINTERPCTRLTYPE_FN_CALL_CLEANUP);
        if (RT_SUCCESS(rc))
        {
            /* Create function call frame and set it up. */
            PVDSCRIPTINTERPFNCALL pFnCall = (PVDSCRIPTINTERPFNCALL)RTMemAllocZ(sizeof(VDSCRIPTINTERPFNCALL));
            if (pFnCall)
            {
                pFnCall->pCaller = pThis->pFnCallCurr;
                pFnCall->ScopeRoot.pParent = NULL;
                pFnCall->ScopeRoot.hStrSpaceVar = NULL;
                pFnCall->pScopeCurr = &pFnCall->ScopeRoot;

                /* Add the variables, remember order. The first variable in the argument has the value at the top of the value stack. */
                PVDSCRIPTASTFNARG pArg = RTListGetFirst(&pAstFn->ListArgs, VDSCRIPTASTFNARG, Core.ListNode);
                for (unsigned i = 0; i < pAstFn->cArgs; i++)
                {
                    PVDSCRIPTINTERPVAR pVar = (PVDSCRIPTINTERPVAR)RTMemAllocZ(sizeof(VDSCRIPTINTERPVAR));
                    if (pVar)
                    {
                        pVar->Core.pszString = pArg->pArgIde->aszIde;
                        pVar->Core.cchString = pArg->pArgIde->cchIde;
                        vdScriptInterpreterPopValue(pThis, &pVar->Value);
                        bool fInserted = RTStrSpaceInsert(&pFnCall->ScopeRoot.hStrSpaceVar, &pVar->Core);
                        Assert(fInserted); RT_NOREF_PV(fInserted);
                    }
                    else
                    {
                        rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory creating a variable");
                        break;
                    }
                    pArg = RTListGetNext(&pAstFn->ListArgs, pArg, VDSCRIPTASTFNARG, Core.ListNode);
                }

                if (RT_SUCCESS(rc))
                {
                    /*
                     * Push compount statement on the control stack and make the newly created
                     * call frame the current one.
                     */
                    rc = vdScriptInterpreterPushAstEntry(pThis, &pAstFn->pCompoundStmts->Core);
                    if (RT_SUCCESS(rc))
                        pThis->pFnCallCurr = pFnCall;
                }

                if (RT_FAILURE(rc))
                {
                    RTStrSpaceDestroy(&pFnCall->ScopeRoot.hStrSpaceVar, vdScriptInterpreterVarSpaceDestroy, NULL);
                    RTMemFree(pFnCall);
                }
            }
            else
                rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory creating a call frame");
        }
    }
    else
    {
        /* External function call, build the argument list. */
        if (pFn->cArgs)
        {
            PVDSCRIPTARG paArgs = (PVDSCRIPTARG)RTMemAllocZ(pFn->cArgs * sizeof(VDSCRIPTARG));
            if (paArgs)
            {
                for (unsigned i = 0; i < pFn->cArgs; i++)
                    vdScriptInterpreterPopValue(pThis, &paArgs[i]);

                rc = pFn->Type.External.pfnCallback(paArgs, pFn->Type.External.pvUser);
                RTMemFree(paArgs);
            }
            else
                rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS,
                                              "Out of memory creating argument array for external function call");
        }
        else
            rc = pFn->Type.External.pfnCallback(NULL, pFn->Type.External.pvUser);
    }

    return rc;
}

/**
 * Evaluate interpreter control statement.
 *
 * @returns VBox status code.
 * @param   pThis      The interpreter context.
 * @param   pCtrl      The control entry to evaluate.
 */
static int vdScriptInterpreterEvaluateCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTINTERPCTRL pCtrl)
{
    int rc = VINF_SUCCESS;

    Assert(!pCtrl->fEvalAst);
    switch (pCtrl->Ctrl.enmCtrlType)
    {
        case VDSCRIPTINTERPCTRLTYPE_FN_CALL:
        {
            PVDSCRIPTFN pFn = pCtrl->Ctrl.FnCall.pFn;

            vdScriptStackPop(&pThis->StackCtrl);
            rc = vdScriptInterpreterFnCall(pThis, pFn);
            break;
        }
        case VDSCRIPTINTERPCTRLTYPE_FN_CALL_CLEANUP:
        {
            vdScriptStackPop(&pThis->StackCtrl);

            /* Delete function call entry. */
            AssertPtr(pThis->pFnCallCurr);
            PVDSCRIPTINTERPFNCALL pFnCallFree = pThis->pFnCallCurr;

            pThis->pFnCallCurr = pFnCallFree->pCaller;
            Assert(pFnCallFree->pScopeCurr == &pFnCallFree->ScopeRoot);
            RTStrSpaceDestroy(&pFnCallFree->ScopeRoot.hStrSpaceVar, vdScriptInterpreterVarSpaceDestroy, NULL);
            RTMemFree(pFnCallFree);
            break;
        }
        case VDSCRIPTINTERPCTRLTYPE_COMPOUND:
        {
            if (!pCtrl->Ctrl.Compound.pStmtCurr)
            {
                /* Evaluated last statement, cleanup and remove the control statement from the stack. */
                vdScriptInterpreterScopeDestroyCurr(pThis);
                vdScriptStackPop(&pThis->StackCtrl);
            }
            else
            {
                /* Push the current statement onto the control stack and move on. */
                rc = vdScriptInterpreterPushAstEntry(pThis, &pCtrl->Ctrl.Compound.pStmtCurr->Core);
                if (RT_SUCCESS(rc))
                {
                    pCtrl->Ctrl.Compound.pStmtCurr = RTListGetNext(&pCtrl->Ctrl.Compound.pStmtCompound->Compound.ListStmts,
                                                                   pCtrl->Ctrl.Compound.pStmtCurr, VDSCRIPTASTSTMT, Core.ListNode);
                }
            }
            break;
        }
        case VDSCRIPTINTERPCTRLTYPE_LOOP:
        {
            PVDSCRIPTASTSTMT pLoopStmt = (PVDSCRIPTASTSTMT)pCtrl->Ctrl.pAstNode;

            /* Check whether the condition passed. */
            VDSCRIPTARG Cond;
            vdScriptInterpreterPopValue(pThis, &Cond);
            AssertMsg(Cond.enmType == VDSCRIPTTYPE_BOOL,
                      ("Value on stack is not of boolean type\n"));

            if (Cond.f)
            {
                /* Execute the loop another round. */
                if (pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_WHILE)
                {
                    rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->While.pCond->Core);
                    if (RT_SUCCESS(rc))
                    {
                        rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->While.pStmt->Core);
                        if (RT_FAILURE(rc))
                            vdScriptStackPop(&pThis->StackCtrl);
                    }
                }
                else
                {
                    AssertMsg(pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_FOR,
                              ("Not a for statement\n"));

                    rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->For.pExprCond->Core);
                    if (RT_SUCCESS(rc))
                    {
                        rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->For.pExpr3->Core);
                        if (RT_SUCCESS(rc))
                        {
                            rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->For.pStmt->Core);
                            if (RT_FAILURE(rc))
                                vdScriptStackPop(&pThis->StackCtrl);
                        }

                        if (RT_FAILURE(rc))
                            vdScriptStackPop(&pThis->StackCtrl);
                    }
                }
            }
            else
                vdScriptStackPop(&pThis->StackCtrl); /* Remove loop control statement. */
            break;
        }
        case VDSCRIPTINTERPCTRLTYPE_IF:
        {
            PVDSCRIPTASTSTMT pIfStmt = (PVDSCRIPTASTSTMT)pCtrl->Ctrl.pAstNode;

            vdScriptStackPop(&pThis->StackCtrl); /* Remove if control statement. */

            /* Check whether the condition passed. */
            VDSCRIPTARG Cond;
            vdScriptInterpreterPopValue(pThis, &Cond);
            AssertMsg(Cond.enmType == VDSCRIPTTYPE_BOOL,
                      ("Value on stack is not of boolean type\n"));

            if (Cond.f)
            {
                /* Execute the true branch. */
                rc = vdScriptInterpreterPushAstEntry(pThis, &pIfStmt->If.pTrueStmt->Core);
            }
            else if (pIfStmt->If.pElseStmt)
                rc = vdScriptInterpreterPushAstEntry(pThis, &pIfStmt->If.pElseStmt->Core);

            break;
        }
        default:
            AssertMsgFailed(("Invalid evaluation control type on the stack: %d\n",
                             pCtrl->Ctrl.enmCtrlType));
    }

    return rc;
}

/**
 * The interpreter evaluation core loop.
 *
 * @returns VBox status code.
 * @param   pThis      The interpreter context.
 */
static int vdScriptInterpreterEvaluate(PVDSCRIPTINTERPCTX pThis)
{
    int rc = VINF_SUCCESS;
    PVDSCRIPTINTERPCTRL pCtrl = NULL;

    pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
    while (pCtrl)
    {
        if (pCtrl->fEvalAst)
        {
            PVDSCRIPTASTCORE pAstNode = pCtrl->pAstNode;
            vdScriptStackPop(&pThis->StackCtrl);

            rc = vdScriptInterpreterEvaluateAst(pThis, pAstNode);
        }
        else
            rc = vdScriptInterpreterEvaluateCtrlEntry(pThis, pCtrl);

        pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
    }

    return rc;
}

DECLHIDDEN(int) vdScriptCtxInterprete(PVDSCRIPTCTXINT pThis, const char *pszFn,
                                      PVDSCRIPTARG paArgs, unsigned cArgs,
                                      PVDSCRIPTARG pRet)
{
    RT_NOREF1(pRet);
    int rc = VINF_SUCCESS;
    VDSCRIPTINTERPCTX InterpCtx;
    PVDSCRIPTFN pFn = NULL;

    AssertPtrReturn(pThis, VERR_INVALID_POINTER);
    AssertPtrReturn(pszFn, VERR_INVALID_POINTER);
    AssertReturn(   (!cArgs && !paArgs)
                 || (cArgs && paArgs), VERR_INVALID_PARAMETER);

    InterpCtx.pScriptCtx  = pThis;
    InterpCtx.pFnCallCurr = NULL;
    vdScriptStackInit(&InterpCtx.StackValues, sizeof(VDSCRIPTARG));
    vdScriptStackInit(&InterpCtx.StackCtrl, sizeof(VDSCRIPTINTERPCTRL));

    pFn = (PVDSCRIPTFN)RTStrSpaceGet(&pThis->hStrSpaceFn, pszFn);
    if (pFn)
    {
        if (cArgs == pFn->cArgs)
        {
            /* Push the arguments onto the stack. */
            /** @todo Check expected and given argument types. */
            for (unsigned i = 0; i < cArgs; i++)
            {
                PVDSCRIPTARG pArg = (PVDSCRIPTARG)vdScriptStackGetUnused(&InterpCtx.StackValues);
                *pArg = paArgs[i];
                vdScriptStackPush(&InterpCtx.StackValues);
            }

            if (RT_SUCCESS(rc))
            {
                /* Setup function call frame and parameters. */
                rc = vdScriptInterpreterFnCall(&InterpCtx, pFn);
                if (RT_SUCCESS(rc))
                {
                    /* Run the interpreter. */
                    rc = vdScriptInterpreterEvaluate(&InterpCtx);
                    vdScriptStackDestroy(&InterpCtx.StackValues);
                    vdScriptStackDestroy(&InterpCtx.StackCtrl);
                }
            }
        }
        else
            rc = vdScriptInterpreterError(&InterpCtx, VERR_INVALID_PARAMETER, RT_SRC_POS, "Invalid number of parameters, expected %d got %d", pFn->cArgs, cArgs);
    }
    else
        rc = vdScriptInterpreterError(&InterpCtx, VERR_NOT_FOUND, RT_SRC_POS, "Function with identifier \"%s\" not found", pszFn);


    return rc;
}
