/* === TeaScript Host Application ===
 * SPDX-FileCopyrightText:  Copyright (C) 2025 Florian Thake <contact |at| tea-age.solutions>.
 * SPDX-License-Identifier: AGPL-3.0-only
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation, version 3.
 * 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 Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>
 */


// C:\Program Files( x86 )\Windows Kits\10\Include\10.0.19041.0\um\winbase.h( 9531, 5 ) : 
// warning C5105 : macro expansion producing 'defined' has undefined behavior( compiling source file source\TestTeaScriptCore_Main.cpp )
// from WinBase.h: #define MICROSOFT_WINDOWS_WINBASE_H_DEFINE_INTERLOCKED_CPLUSPLUS_OVERLOADS (_WIN32_WINNT >= 0x0502 || !defined(_WINBASE_))
#if defined( _MSC_VER )
# include <sdkddkver.h> // WDK_NTDDI_VERSION
# if WDK_NTDDI_VERSION < 0x0A00000A  // NTDDI_WIN10_FE
#  pragma warning( push )
#  pragma warning( disable: 5105 )
#  pragma message( "USNING OLD WINSDK!!" )
# endif
#endif

//for VS use /Zc:__cplusplus
#if __cplusplus < 202002L
# if defined _MSVC_LANG // fallback without /Zc:__cplusplus
#  if !_HAS_CXX20
#   error must use at least C++20
#  endif
# else
#  error must use at least C++20
# endif
#endif

// Undefine for set to default JSON adapter. If defined, BSON support is available via the nlohmann JSON adapter.
#define TEASCRIPT_JSON_FLAVOR            TEASCRIPT_JSON_NLOHMANN

// Undefine or set to 0 for disable the WebPreview module
#define TEASCRIPT_ENGINE_USE_WEB_PREVIEW       1

// Undefine or set to 0 for disable colorized output
#define TEASCRIPT_USE_FMT_COLOR     1


// Actually we are dependent of external {fmt} lib. But we use it as header only...
#if __has_include( "fmt/format.h" )
# ifndef FMT_HEADER_ONLY
#  define FMT_HEADER_ONLY
# endif
# include "fmt/format.h"
# if FMT_VERSION < 100101
#  error format lib must be at least 10.1.1
# endif
# include "fmt/chrono.h"
# if TEASCRIPT_USE_FMT_COLOR
#  include "fmt/color.h"
#endif
#else
#  error no format lib found!
#endif


#include "teascript/Parser.hpp"
#include "teascript/StackVMCompiler.hpp"
#include "teascript/StackMachine.hpp"
#include "teascript/Context.hpp"
#include "teascript/CoreLibrary.hpp"
#if TEASCRIPT_ENGINE_USE_WEB_PREVIEW
# include "teascript/modules/WebPreview.hpp"
#endif
#include "teascript/version.h"


#include <cstdlib> // EXIT_SUCCESS
#include <cstdio>
#include <csignal>
#include <iostream>
#include <fstream>
#include <vector>
#include <list>
#include <filesystem>
#include <chrono>

// check for readline lib (must be after <iostream> !)
#if __has_include( <readline/readline.h> )
# include <readline/history.h>
# include <readline/readline.h>
# define TEASCRIPT_HAS_READLINE    1
#endif


#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#define NOGDICAPMASKS
#define NOVIRTUALKEYCODES
#define NOWINMESSAGES
#define NOWINSTYLES
#define NOSYSMETRICS
#define NOMENUS
#define NOICONS
#define NOKEYSTATES
#define NOSYSCOMMANDS
#define NORASTEROPS
#define NOSHOWWINDOW
#define OEMRESOURCE
#define  NOATOM
#define  NOCLIPBOARD
#define  NOCOLOR
#define  NOCTLMGR
#define  NODRAWTEXT
#define NOGDI
#define  NOKERNEL
#define NOUSER
//#define  NONLS
#define  NOMB
#define  NOMEMMGR
#define NOMETAFILE
#define NOMINMAX
#define  NOMSG 
#define NOOPENFILE
#define NOSCROLL
#define  NOSERVICE
#define NOSOUND
#define  NOTEXTMETRIC
#define NOWH
#define NOWINOFFSETS
#define NOCOMM
#define  NOKANJI
#define  NOHELP
#define  NOPROFILER
#define  NODEFERWINDOWPOS
#define  NOMCX
//#include <stringapiset.h> // WIN32 MultiByteTo...
//#include <fileapi.h> //  AreFileApisANSI() ? CP_ACP : CP_OEMCP;
#include <Windows.h>
#endif

#if defined( _MSC_VER )
# if WDK_NTDDI_VERSION < 0x0A00000A  // NTDDI_WIN10_FE
#  pragma warning( pop ) // for make compiler happy (warning)
# endif
// safety guard!
# if NTDDI_VERSION != NTDDI_WIN10_VB
#  error Wrong NTDDI_VERSION
# endif
#endif


#if 0 // check on Linux again...
volatile static std::sig_atomic_t GlobalRunning = 1;

// we want to react on Ctrl+C (exit)
void signal_handler( int const sig )
{
    switch( sig ) {
    case SIGINT:
    case SIGTERM:
        GlobalRunning = 0;
        break;
    default:
        ((void)0);
    }
}

void set_signal_handler()
{
#if defined( _WIN32 ) // have a crash on Windows, looks like a race condition...
    //signal( SIGINT, SIG_IGN );
    signal( SIGINT, signal_handler );
    signal( SIGTERM, signal_handler );
#else
    signal( SIGINT, signal_handler );
    signal( SIGTERM, signal_handler );
#endif
}
#endif


std::string copyright_info()
{
    return std::string( teascript::copyright_info() ) + " See LICENSE.TXT.";
}


bool print_ast( teascript::ASTNode const *node, int depth )
{
    std::string ident = "|";

    while( depth > 1 ) {
        ident += " |";
        --depth;
    }

#if TEASCRIPT_USE_FMT_COLOR
    fmt::print( fmt::fg( fmt::color::wheat ), "{}-{}\n", ident, fmt::styled( node->GetInfoStr(), fmt::fg( fmt::color::white_smoke ) ) );
#else
    std::cout << ident << "-" << node->GetInfoStr() << std::endl;
#endif

    return true;
}

void print_logo()
{
#if 0
    constexpr char logo_str[] = R"LOGO(
 _____          _____           _       _   
|_   _|        /  ___|         (_)     | |  
  | | ___  __ _\ `--.  ___ _ __ _ _ __ | |_ 
  | |/ _ \/ _` |`--. \/ __| '__| | '_ \| __|
  | |  __/ (_| /\__/ / (__| |  | | |_) | |_ 
  \_/\___|\__,_\____/ \___|_|  |_| .__/ \__|
                                 | |        
                                 |_|        
)LOGO";
#elif 1
    constexpr char logo_str[] = R"LOGO(
████████╗███████╗ █████╗ ███████╗ ██████╗██████╗ ██╗██████╗ ████████╗
╚══██╔══╝██╔════╝██╔══██╗██╔════╝██╔════╝██╔══██╗██║██╔══██╗╚══██╔══╝
   ██║   █████╗  ███████║███████╗██║     ██████╔╝██║██████╔╝   ██║   
   ██║   ██╔══╝  ██╔══██║╚════██║██║     ██╔══██╗██║██╔═══╝    ██║   
   ██║   ███████╗██║  ██║███████║╚██████╗██║  ██║██║██║        ██║   
   ╚═╝   ╚══════╝╚═╝  ╚═╝╚══════╝ ╚═════╝╚═╝  ╚═╝╚═╝╚═╝        ╚═╝   
)LOGO";
#else
    constexpr char logo_str[] = R"LOGO(
88888888888                 .d8888b.                   d8b          888    
    888                    d88P  Y88b                  Y8P          888    
    888                    Y88b.                                    888    
    888   .d88b.   8888b.   "Y888b.    .d8888b 888d888 888 88888b.  888888 
    888  d8P  Y8b     "88b     "Y88b. d88P"    888P"   888 888 "88b 888    
    888  88888888 .d888888       "888 888      888     888 888  888 888    
    888  Y8b.     888  888 Y88b  d88P Y88b.    888     888 888 d88P Y88b.  
    888   "Y8888  "Y888888  "Y8888P"   "Y8888P 888     888 88888P"   "Y888 
                                                           888             
                                                           888             
                                                           888              
)LOGO";
#endif
#if TEASCRIPT_USE_FMT_COLOR
    fmt::print( fmt::fg( fmt::color::wheat ), "{}", logo_str );
#else
    fmt::print( "{}", logo_str );
#endif
}

void print_parameters()
{
    fmt::print( "\n{0}, version {1}.{2}.{3}.\n"
                "{4}\n\n"
                "{0}                            -->  without arguments interactive shell is launched.\n"
                "{0} [params] filename [args]   -->  executes 'filename' (.tea|.tsb) with 'args' as script arguments.\n"
                "{0} [params] --compile file    -->  compiles script and saves the TeaScript Binary as file.tsb\n"
                "{0} --printAST filename        -->  parses 'filename', prints the AST and measures parsing time.\n"
                "{0} --printTSVM filename       -->  compiles 'filename' (or loads .tsb), and prints the TSVM instructions.\n"
                "{0} --info filename.tsb        -->  prints header information of a compiled TeaScript binary (.tsb)\n"
                "{0} [params] --batch filename  -->  processes 'filename' as batch commands before interactive shell.\n"
                "{0} [-i] [--no-eval]           -->  implicit launches interactive shell if no file name is given\n"
                "{0} [params] --shell [file]    -->  explicit launches interactive shell, optional file is a batch file.\n"
                "{0} [params] --exec file [args]-->  explicit sets mode to script execution mode.\n"
                "{0} -v,--version               -->  prints version information and exit.\n"
                "{0} -?,-h,/?,--help            -->  prints this help message.\n"
                "\n[params]:\n"
                "-D,--debug          --> enables debug mode (and sets optimization level to Debug)\n"
                "--silent            --> enables silent mode.\n"
                "-T,--time           --> enables time measurement (bootstrap/parsing/compile/eval|exec).\n"
                "-i,--interactive    --> launches interactive shell after script file has been executed.\n"
                "--no-eval           --> compile mode (default for execution mode).\n"
                "--eval|--no-compile --> eval mode (default for interactive shell).\n"
                "-O0|O1|O2           --> sets the optimization level for compilation.\n"
                "--config=c          --> uses CoreLibrary config c. c must be hexadecimal starting with 0x\n"
                "--                  --> ends [params], next parameter is filename.\n"
                "--old-deepcopy-parameters --> activates the old parameters are copy assign by default behavior.\n"
                "                              This setting is only for transition and will be removed.\n"
                "--legacy-args       --> activates the legacy script arguments form \"arg1\", \"arg2\", ...\n"
                "                        instead of the tuple \"args[idx]\".\n"
                "--no-logo           --> don't show ascii logo in interactive shell.\n"
                "\nFurther support is available at: <{5}> \n", teascript::self_name_str(),
                teascript::version::get_major(), teascript::version::get_minor(), teascript::version::get_patch(), 
                copyright_info(), teascript::contact_info() );
    fmt::print( "\nNew versions of this TeaScript Host Application for execute standalone TeaScript files\n"
                "will be made available here: https://tea-age.solutions/downloads/ \n" );
}

void show_help()
{
    fmt::print( "All internal commands start with a colon :, here follows a list.\n\n"
                ":reset   -> resets the Parser state _and_ the context (e.g. clears all variables, etc.)\n"
                ":reset=c -> as reset but uses CoreLibrary config c. c must be hexadecimal starting with 0x\n"
                ":debug   -> toggles debug mode. debug mode keeps source lines, uses extra instructions and\n"
                "            does not do any optimization of the code.\n"
                ":silent  -> toggles silent mode. in silent mode the immediate printing of last value is disabled.\n"
                ":debug ast|tsvm  -> toggles printing parsed AST | current executing instruction.\n"
                ":ls vars         -> list all variables/functions of all present scopes.\n"
                ":search <str>    -> list all variables/functions of all present scopes containing <str>.\n"
                ":inspect <str>   -> inspects function <str> by printing its AST or TSVM instructions.\n\n"
                ":load=<file>     -> loads <file> for manually operating on that file (parse,exec,show,...).\n"
                ":show <n>        -> shows n lines from current position of loaded file. 0 == with marked column.\n"
                ":goto line[,col] -> goes to line and column of loaded file if position exists. col defaults to 1.\n"
                ":parse <n>       -> parses n lines from current position of loaded file into an AST.\n"
                ":eval <n>        -> evaluates next n statements of already parsed statements of loaded file.\n"
                ":stmt <n>        -> prints AST of an already parsed (top-level) statement of loaded file.\n"
                "                    use 0 for show the actual amount of present statements.\n"
                ":compile         -> compiles the parsed AST parts to a TeaStackVM program.\n"
                ":disasm [start,end] -> disassembles a compiled program and prints the TSVM instructions.\n"
                ":constraint <n>[ms] -> sets constraint of execution to <n> max instructions [or max milliseconds]\n"
                ":constraint none    -> sets constraint of execution to nothing.\n"
                ":exec|x          -> executes the compiled program until Finished|Suspended or Halted.\n"
                ":continue|c      -> continues a suspended program.\n\n"
                ":dbinfo          -> prints best matching debug info of current program position.\n"
                ":dbinfo <n>      -> prints debug info of given instruction index of current program.\n"
                ":callstack       -> prints current callstack of Suspended|Halted program.\n"
                ":pc              -> prints current instruction (program counter) of Suspended|Halted program.\n\n"
                ":q|quit|exit -> exits the interactive {} shell.\n"
                ":?|help      -> shows this help message.\n\n"
                "Everything else not starting with a colon will be parsed and evaluated as TeaScript code.\n\n"
                "Further support is available at: <{}> \n", teascript::self_name_str(), teascript::contact_info() );
}


// config obtained by parameter parsing. some values are needed only once (mode), others, like debug, silent, etc. are maintained in interactive shell during execution.
struct Config
{
    enum class Mode
    {
        Unknown,
        Error,   //explicit error  flag
        Shell,
        Exec,
        Compile,
        Parse,
        PrintCompile,
        PrintInfo,
        PrintVersion,
        PrintParameters,
    } mode = Mode::Unknown;
    teascript::eOptimize  opt_level = teascript::eOptimize::O0;
    bool  no_compile = false;
    bool  debug = false;
    bool  silent = false;
    bool  interactive = false;
    bool  time_measure = false; // time measurement for Exec (Parse always does this)
    bool  old_mutable_parameters = false; // activates the old function parameters are mutable by default behavior (is only a temporary setting for transition)
    bool  old_deepcopy_parameters = false; // activates the old function parameters are copy assign by default behavior (is only a temporary setting for transition)
    bool  legacy_args = false;   // activates legacy form of script parameters "arg1", "arg2", ...
    bool  no_logo = false;
    teascript::config::eConfig  core_config = teascript::config::LevelFull;
    std::string filename;
    std::vector<std::string> script_args;
};

// the context when manually loading and operating on a file.
struct PartialParseState
{
    bool print_ast  = false;
    bool print_tsvm = false;
    std::string         filename;
    std::string         storage;
    teascript::Content  content;
    teascript::Parser   parser;
    std::list< teascript::ASTNodePtr >  parts;
    teascript::StackVM::ProgramPtr      program;
    std::shared_ptr< teascript::StackVM::Machine<false> > machine;
    teascript::StackVM::Constraints  constraints; // defaults to None
};

void unload_file( PartialParseState &state )
{
    state.content = teascript::Content();
    state.filename.clear();
    state.storage.clear();
    state.parser.ClearState();
    state.parts.clear();
    state.program.reset();
    state.machine->Reset();
}

void load_file( std::string const &file, PartialParseState &state )
{
    try {
        unload_file( state );
        auto val = teascript::CoreLibrary::ReadTextFile( file );
        if( val.GetTypeInfo()->IsSame<bool>() ) {
            throw std::runtime_error( "" );
        }
        state.storage = val.GetValue<std::string>();
        state.content = teascript::Content( state.storage );
    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not load file! {}\n", ex.what() );
        return;
    }
    state.filename = file;
    teascript::Content tmp = state.content;
    long long lines = 0;
    while( tmp.HasMore() ) {
        tmp.MoveToLineFeed();
        ++lines;
        auto const cur = tmp[0]; // memory for last empty line check.
        ++tmp; // next line
        // check for a possible last empty line (if the last line ends with new line)
        if( not tmp.HasMore() && cur == '\n' ) {
            ++lines; // count the last (empty) one as well
        }
    }
    fmt::print( "<<< file loaded, {} lines. ok.\n", lines );
}


void reload( PartialParseState &state )
{
    // do this always without check sth. was loaded.
    state.parser.ClearState();
    state.parts.clear();
    state.content = teascript::Content( state.storage );
    // BUT keep the program if we don't have the source to re-compile it!!
    if( not state.storage.empty() ) {
        state.program.reset();
    }
    state.machine->Reset();
}


void show_lines( std::string const &lines, PartialParseState &state )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to show. Load a file first!" );
        }

        auto n = std::stoull( lines );
        if( 0 == n ) {
#if TEASCRIPT_USE_FMT_COLOR
            teascript::util::debug_print_currentline_colored( state.content, true );
#else
            teascript::util::debug_print_currentline( state.content, true );
#endif
            return;
        }
        auto c = state.content;
        auto l = c.CurrentLine();
        while( n > 0 && c.HasMore() ) {
#if TEASCRIPT_USE_FMT_COLOR
            fmt::print( fmt::fg( fmt::color::wheat ), "{:04} : {}\n", l++, fmt::styled( teascript::util::extract_current_line( c ), fmt::fg( fmt::color::white_smoke ) ) );
#else
            fmt::print( "{:04} : {}\n", l++, teascript::util::extract_current_line( c ) );
#endif
            c.MoveToLineFeed();
            ++c;
            --n;
        }

    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not show lines! {}\n", ex.what() );
    }
}

void goto_pos( std::string const &pos, PartialParseState &state )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to 'goto'. Load a file first!" );
        }
        unsigned int line = 1, col = 1;
#if defined(_MSC_VER)
        auto const scanned = sscanf_s( pos.c_str(), "%u,%u", &line, &col );
#else
        auto const scanned = sscanf( pos.c_str(), "%u,%u", &line, &col );
#endif
        if( scanned < 1 ) {
            throw std::runtime_error( "Must be the form: goto line[,col]" );
        } else if( line == 0 || col == 0 ) {
            throw std::runtime_error( "Line and column must be a positive number!" );
        }

        state.content.GoTo( static_cast<long long>(line), static_cast<long long>(col) );

        show_lines( "0", state );

    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not 'goto' position! {}\n", ex.what() );
    }
}

void show_stmt( std::string const &no, PartialParseState &state )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to show. Load a file first!" );
        }

        // number, not idx (starts at 1)!
        auto n = std::stoull( no );

        if( n > state.parts.size() ) {
            throw std::out_of_range( "Not enough statements available!" );
        } else if( 0 == n ) {
            fmt::print( "<<< {} remaining statements.\n", state.parts.size() );
            return;
        }

        auto it = state.parts.begin();
        if( n > 1 ) {
            std::advance( it, n - 1 );
        }
#if TEASCRIPT_USE_FMT_COLOR
        fmt::print( fmt::fg( fmt::color::wheat ), "AST:\n" );
#else
        fmt::print( "AST:\n" );
#endif
        (*it)->Apply( print_ast );

    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not show statement! {}\n", ex.what() );
    }
}

void parse_lines( std::string const &lines, PartialParseState &state )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to parse. Load a file first!" );
        }

        auto n = std::stoull( lines );
        if( 0 == n ) {
            throw std::out_of_range( "Must be a positive number!" );
        } else if( not state.content.HasMore() ) {
            throw std::runtime_error( "No more content to parse!" );
        }

        auto start = state.content;
        auto end = start;
        while( n > 0 && end.HasMore() ) {
            end.MoveToLineFeed();
            if( n > 1 ) {
                ++end; // next line
            }
            --n;
        }

        auto c = start.SubContent( 0, &(*end) - &(*start) );
        c.SetLineOffset( start.CurrentLine() - 1 );
        state.parser.ParsePartial( c, state.filename );

        // add new node(s) 'sub-routine'
        auto node_handler = [&state]( auto node ) mutable {
            if( node->begin() != node->end() ) {
                if( state.print_ast ) {
#if TEASCRIPT_USE_FMT_COLOR
                    fmt::print( fmt::fg( fmt::color::wheat ), "AST:\n" );
#else
                    fmt::print( "AST:\n" );
#endif
                    node->Apply( print_ast );
                }
                state.parts.insert( state.parts.end(), node->begin(), node->end() );
            }
            return std::distance( node->begin(), node->end() );
        };

        auto count = node_handler( state.parser.GetPartialParsedASTNodes() );

        state.content = end;
        ++state.content; // next line

        // special case: end of script with an if will produce a node here:
        if( not state.content.HasMore() ) {
            count += node_handler( state.parser.GetFinalPartialParsedASTNodes() );
        }

        if( not state.content.HasMore() ) {
            fmt::print( "<<< Script end reached. {} new statements parsed.\n", count );
        } else {
            fmt::print( "<<< {} new statements parsed.\n", count );
        }

    } catch( teascript::exception::runtime_error const &ex ) {
        fmt::print( "<<< " );
#if TEASCRIPT_USE_FMT_COLOR
        teascript::util::pretty_print_colored( ex );
#else
        teascript::util::pretty_print( ex );
#endif
        state.parser.ClearState();
    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not parse lines! {}\n", ex.what() );
    }
}

void eval_stmts( std::string const &count, PartialParseState &state, teascript::Context & rContext, bool silent )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to execute. Load a file first!" );
        }
        auto n = std::stoull( count );
        if( state.parts.size() < n ) {
            throw std::out_of_range( "Not enough statements available!" );
        }

        teascript::ValueObject res;
        
        while( n > 0 ) {
            res = state.parts.front()->Eval( rContext );
            state.parts.pop_front();

            --n;
        }
        if( not silent && res.HasPrintableValue() ) {
#if TEASCRIPT_USE_FMT_COLOR
            fmt::print( "<<< {}\n", fmt::styled( res.PrintValue(), fmt::fg( fmt::color::light_sky_blue ) ) );
#else
            fmt::print( "<<< {}\n", res.PrintValue() );
#endif
        }
        fmt::print( "<<< Statements executed. {} remaining statements.\n", state.parts.size() );
    } catch( teascript::control::Exit_Script const &ex ) {
#if TEASCRIPT_USE_FMT_COLOR
        fmt::print( "<<< {}\n", fmt::styled( ex.GetResult().PrintValue(), fmt::fg( fmt::color::light_sky_blue ) ) );
#else
        fmt::print( "<<< {}\n", ex.GetResult().PrintValue() );
#endif
    } catch( teascript::control::Return_From_Function const &rReturn ) {
#if TEASCRIPT_USE_FMT_COLOR
        fmt::print( "<<< {}\n", fmt::styled( rReturn.GetResult().PrintValue(), fmt::fg( fmt::color::light_sky_blue ) ) );
#else
        fmt::print( "<<< {}\n", rReturn.GetResult().PrintValue() );
#endif
    } catch( teascript::exception::runtime_error const &ex ) {
        fmt::print( "<<< " );
#if TEASCRIPT_USE_FMT_COLOR
        teascript::util::pretty_print_colored( ex );
#else
        teascript::util::pretty_print( ex );
#endif
    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not execute statements! {}\n", ex.what() );
    }
}


void print_tsvm_instr( teascript::StackVM::Instruction const &rInstruction, size_t const no, bool const mark = false )
{
    using namespace teascript;
    std::string str;
    switch( rInstruction.instr ) {
    case eTSVM_Instr::HALT:         str = "HALT"; break;
    case eTSVM_Instr::ProgramEnd:   str = "Program End"; break;
    case eTSVM_Instr::NoOp:         str = "NoOp"; break;
    case eTSVM_Instr::NoOp_NaV:     str = "NoOp_NaV"; break;
    case eTSVM_Instr::Debug:        str = "Debug (" + rInstruction.payload.PrintValue() + ")"; break;
    case eTSVM_Instr::ExprStart:    str = "ExprStart"; break;
    case eTSVM_Instr::ExprEnd:      str = "ExprEnd"; break;
    case eTSVM_Instr::RepeatStart:  str = "RepeatStart " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::RepeatEnd:    str = "RepeatEnd " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::If:           str = "If"; break;
    case eTSVM_Instr::Else:         str = "Else"; break;
    case eTSVM_Instr::Push:         str = "Push " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::Pop:          str = "Pop"; break;
    case eTSVM_Instr::Replace:      str = "Replace " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::Swap:         str = "Swap"; break;
    case eTSVM_Instr::Load:         str = "Load " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::Stor:         str = "Stor     (shared: " + rInstruction.payload.PrintValue() + ")"; break;
    case eTSVM_Instr::DefVar:       str = "DefVar   (shared: " + rInstruction.payload.PrintValue() + ")"; break;
    case eTSVM_Instr::ConstVar:     str = "ConstVar (shared: " + rInstruction.payload.PrintValue() + ")"; break;
    case eTSVM_Instr::AutoVar:      str = "AutoVar  (shared: " + rInstruction.payload.PrintValue() + ")"; break;
    case eTSVM_Instr::UndefVar:     str = "UndefVar " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::IsDef:        str = "IsDef " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::MakeTuple:    str = "MakeTuple (" + rInstruction.payload.PrintValue() + ")"; break;
    case eTSVM_Instr::SetElement:   str = "SetElement   (shared: " + rInstruction.payload.PrintValue() + ")"; break;
    case eTSVM_Instr::DefElement:   str = "DefElement   (shared: " + rInstruction.payload.PrintValue() + ")"; break;
    case eTSVM_Instr::ConstElement: str = "ConstElement (shared: " + rInstruction.payload.PrintValue() + ")"; break;
    case eTSVM_Instr::IsDefElement: str = "IsDefElement " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::UndefElement: str = "UndefElement " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::SubscriptGet: str = "SubscriptGet"; break;
    case eTSVM_Instr::SubscriptSet: str = "SubscriptSet (shared : " + rInstruction.payload.PrintValue() + ")"; break;
    case eTSVM_Instr::UnaryOp:      str = "UnaryOp " + teascript::ASTNode_Unary_Operator::EnumToString( static_cast<teascript::ASTNode_Unary_Operator::eOperation>(rInstruction.payload.GetValue<teascript::U64>() ) ); break;
    case eTSVM_Instr::BinaryOp:     str = "BinaryOp " + teascript::ASTNode_Binary_Operator::EnumToString( static_cast<teascript::ASTNode_Binary_Operator::eOperation>(rInstruction.payload.GetValue<teascript::U64>()) ); break;
    case eTSVM_Instr::IsType:       str = "IsType"; break;
    case eTSVM_Instr::AsType:       str = "AsType"; break;
    case eTSVM_Instr::BitOp:        str = "BitOp " + teascript::ASTNode_Bit_Operator::EnumToString( static_cast<teascript::ASTNode_Bit_Operator::eBitOp>(rInstruction.payload.GetValue<teascript::U64>()) ); break;
    case eTSVM_Instr::DotOp:        str = "DotOp"; break;
    case eTSVM_Instr::EnterScope:   str = "EnterScope"; break;
    case eTSVM_Instr::ExitScope:    str = "ExitScope"; break;
    case eTSVM_Instr::Test:         str = "Test"; break;
    case eTSVM_Instr::JumpRel:      str = "JumpRel       --> " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::JumpRel_If:   str = "JumpRel_If    --> " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::JumpRel_IfNot:str = "JumpRel_IfNot --> " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::TestAndJumpRel_If:   str = "TestAndJumpRel_If    --> " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::TestAndJumpRel_IfNot:str = "TestAndJumpRel_IfNot --> " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::ForallHead:   str = "ForallHead " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::ForallNext:   str = "ForallNext    --> " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::CallFunc:     str = "CallFunc " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::ParamList:    str = "ParamList"; break;
    case eTSVM_Instr::FuncDef:      str = "FuncDef " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::Ret:          str = "Ret"; break;
    case eTSVM_Instr::ParamSpec:    str = "ParamSpec (" + rInstruction.payload.PrintValue() + ")"; break;
    case eTSVM_Instr::ParamSpecClean:str = "ParamSpecClean"; break;
    case eTSVM_Instr::FromParam:    str = "FromParam"; break;
    case eTSVM_Instr::FromParam_Or: str = "FromParam_Or  --> " + rInstruction.payload.PrintValue(); break;
    case eTSVM_Instr::ExitProgram:  str = "ExitProgram"; break;
    case eTSVM_Instr::Suspend:      str = "Suspend"; break;
    case eTSVM_Instr::Yield:        str = "Yield"; break;
    case eTSVM_Instr::Catch:        str = "Catch (" + rInstruction.payload.PrintValue() + ")"; break;
    case eTSVM_Instr::NotImplemented:str = "NotImplemented: " + rInstruction.payload.PrintValue(); break;
    default: 
        str = "<Unknown>";
    }
    fmt::print( fmt::fg( mark ? fmt::color::violet : fmt::color::light_sky_blue ), "{:04}: {}\n", no, fmt::styled( str, fmt::fg( fmt::color::white_smoke ) ) );
}

void print_tsvm_code( teascript::StackVM::InstrContainer const &rInstructions, size_t const start = 0, size_t const end = std::numeric_limits<size_t>::max() )
{
#if TEASCRIPT_USE_FMT_COLOR
    if( start != 0 || end != std::numeric_limits<size_t>::max() ) {
        fmt::print( fmt::fg( fmt::color::wheat ), "\nTEA STACK VM INSTRUCTIONS [{}..{}]:\n", start, end );
    } else {
        fmt::print( fmt::fg( fmt::color::wheat ), "\nTEA STACK VM INSTRUCTIONS:\n" );
    }
#else
    if( start != 0 || end != std::numeric_limits<size_t>::max() ) {
        fmt::print( "\nTEA STACK VM INSTRUCTIONS [{}..{}]:\n", start, end );
    } else {
        fmt::print( "\nTEA STACK VM INSTRUCTIONS:\n" );
    }
#endif

    size_t c = 0;
    
    for( auto const &ins : rInstructions ) {
        if( c >= start && c <= end ) {
            print_tsvm_instr( ins, c );
        } else if( c > end ) {
            break;
        }
        ++c;
    }
}


void compile_ast_to_tsvm( PartialParseState &state, teascript::Context &rContext )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to compile. Load a file first!" );
        }
        if( state.parts.empty() ) {
            throw std::runtime_error( "Nothing to compile. Parse a loaded file first!" );
        }

        auto ast = std::make_shared<teascript::ASTNode_File>( state.filename, state.parts );

        teascript::StackVM::Compiler  compiler;
        state.program = compiler.Compile( ast, rContext.is_debug ? teascript::eOptimize::Debug : teascript::eOptimize::O0 );
        
        fmt::print( "<<< compiled to {} instructions.\n", state.program->GetInstructions().size() );
    } catch( teascript::exception::runtime_error const &ex ) {
        if( ex.IsSourceLocSet() ) { // FIXME: Not all compile_errors have the file name set!
            fmt::print( "<<< Could not compile!\n" );
#if TEASCRIPT_USE_FMT_COLOR
            teascript::util::pretty_print_colored( ex );
#else
            teascript::util::pretty_print( ex );
#endif
        } else {
            fmt::print( "<<< Could not compile! {}\n", ex.what() );
        }
    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not compile! {}\n", ex.what() );
    }
}

void exec_tsvm( PartialParseState &state, teascript::Context &rContext )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to execute. Load a file first!" );
        }
        if( not state.program ) {
            throw std::runtime_error( "Nothing to execute. Compile a program first!" );
        }

#if TEASCRIPT_USE_FMT_COLOR
        fmt::print( fmt::fg( fmt::color::wheat ), "\nEXEC IN TSVM ...\n" );
#else
        fmt::print( "\nEXEC IN TSVM ...\n" );
#endif
        state.machine->Exec( state.program, rContext, state.constraints );

        state.machine->ThrowPossibleErrorException();

        if( state.machine->IsFinished() ) {
            if( true ) {
                auto const &stack = state.machine->GetStack();
                fmt::print( "Debug Info after exec:\nstack capacity: {}, stack size: {}\n", stack.capacity(), stack.size() );
                fmt::print( "stack content:\n" );
                for( auto const &e : stack ) {
                    fmt::print( "{}\n", e.PrintValue() );
                }
                fmt::print( "\nlocal scope count: {}\n", rContext.LocalScopeCount() );
                //fmt::print( "\ncall stack capacity: {}\n", state.machine->GetCallStack().capacity() );
                fmt::print( "\n" );
            }

            auto result = state.machine->GetResult();

            if( result.has_value() && result.value().HasPrintableValue() ) {
#if TEASCRIPT_USE_FMT_COLOR
                fmt::print( "<<< {}\n", fmt::styled( result.value().PrintValue(), fmt::fg( fmt::color::light_sky_blue ) ) );
#else
                fmt::print( "<<< {}\n", result.value().PrintValue() );
#endif
            } else {
                fmt::print( "<<< ok.\n" );
            }
        } else if( state.machine->IsSuspended() ) {
            fmt::print( "<<< program is suspended. use :continue for continue execution.\n" );
            if( state.machine->HasResult() ) {
                auto result = state.machine->GetResult();
#if TEASCRIPT_USE_FMT_COLOR
                fmt::print( "<<< yielded value: {}\n", fmt::styled( result.value().PrintValue(), fmt::fg( fmt::color::light_sky_blue ) ) );
#else
                fmt::print( "<<< yielded value: {}\n", result.value().PrintValue() );
#endif
            }
        } else {
            throw std::runtime_error( "Internal error! Unexpected machine state!" );
        }
    } catch( teascript::exception::runtime_error const &ex ) {
        fmt::print( "<<< " );
#if TEASCRIPT_USE_FMT_COLOR
        teascript::util::pretty_print_colored( ex );
#else
        teascript::util::pretty_print( ex );
#endif
    } catch( std::exception const &ex ) {
        fmt::print( "<<< ERROR! {}\n", ex.what() );
    }
}

void continue_tsvm( PartialParseState &state, teascript::Context &rContext )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to continue. Load a file first!" );
        }
        if( not state.program ) {
            throw std::runtime_error( "Nothing to continue. Compile a program first!" );
        }

#if TEASCRIPT_USE_FMT_COLOR
        fmt::print( fmt::fg( fmt::color::wheat ), "\nCONTINUE IN TSVM ...\n" );
#else
        fmt::print( "\nCONTINUE IN TSVM ...\n" );
#endif
        state.machine->Continue( rContext, state.constraints );

        state.machine->ThrowPossibleErrorException();

        if( state.machine->IsFinished() ) {
            if( true ) {
                auto const &stack = state.machine->GetStack();
                fmt::print( "Debug Info after exec:\nstack capacity: {}, stack size: {}\n", stack.capacity(), stack.size() );
                fmt::print( "stack content:\n" );
                for( auto const &e : stack ) {
                    fmt::print( "{}\n", e.PrintValue() );
                }
                fmt::print( "\nlocal scope count: {}\n", rContext.LocalScopeCount() );
                fmt::print( "\n" );
            }

            auto result = state.machine->GetResult();

            if( result.has_value() && result.value().HasPrintableValue() ) {
#if TEASCRIPT_USE_FMT_COLOR
                fmt::print( "<<< {}\n", fmt::styled( result.value().PrintValue(), fmt::fg( fmt::color::light_sky_blue ) ) );
#else
                fmt::print( "<<< {}\n", result.value().PrintValue() );
#endif
            } else {
                fmt::print( "<<< ok.\n" );
            }
        } else if( state.machine->IsSuspended() ) {
            fmt::print( "<<< program is suspended. use :continue for continue execution.\n" );
            if( state.machine->HasResult() ) {
                auto result = state.machine->GetResult();
#if TEASCRIPT_USE_FMT_COLOR
                fmt::print( "<<< yielded value: {}\n", fmt::styled( result.value().PrintValue(), fmt::fg( fmt::color::light_sky_blue ) ) );
#else
                fmt::print( "<<< yielded value: {}\n", result.value().PrintValue() );
#endif
            }
        } else {
            throw std::runtime_error( "Internal error! Unexpected machine state!" );
        }
    } catch( teascript::exception::runtime_error const &ex ) {
        fmt::print( "<<< " );
#if TEASCRIPT_USE_FMT_COLOR
        teascript::util::pretty_print_colored( ex );
#else
        teascript::util::pretty_print( ex );
#endif
    } catch( std::exception const &ex ) {
        fmt::print( "<<< ERROR! {}\n", ex.what() );
    }
}

void disassemble_program( PartialParseState &state, std::string const &range = "" )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to disassemble. Load a file first!" );
        }
        if( not state.program ) {
            throw std::runtime_error( "Nothing to disassemble. Compile a program first!" );
        }

        size_t start = 0, end = std::numeric_limits<size_t>::max();
#if defined(_MSC_VER)
        auto const scanned = sscanf_s( range.c_str(), "%zu,%zu", &start, &end );
#else
        auto const scanned = sscanf( range.c_str(), "%zu,%zu", &start, &end );
#endif
        if( scanned > 0 and end < start ) { // just swap
            std::swap( start, end );
        }

        print_tsvm_code( state.program->GetInstructions(), start, end );

    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not disassemble! {}\n", ex.what() );
    }
}

void show_current_debuginfo( PartialParseState &state, std::string const &no = "" )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to show. Load a file first!" );
        }
        if( not state.program ) {
            throw std::runtime_error( "Nothing to show. Compile a program first!" );
        }
        if( not state.machine->IsSuspended() and not state.machine->IsErroneousHalted() ) {
            throw std::runtime_error( "Nothing to show. Program must be suspended or halted." );
        }

        size_t const instr = no.empty() ? state.machine->GetCurrentInstructionIndex() : std::stoull( no );

        if( state.machine->GetCurrentProgram()->GetInstructions().size() < instr ) {
            throw std::runtime_error( "Instruction points to illegal program address!" );
        }

        auto const loc = state.machine->GetCurrentProgram()->GetBestMatchingSourceLocationFor( instr );
        if( loc.IsSet() ) {
#if TEASCRIPT_USE_FMT_COLOR
            fmt::print( "debug info: file \"{}\"\nin line {}, column {}:\n", fmt::styled( loc.GetFileName(), fmt::fg( fmt::color::white_smoke ) ),
                        fmt::styled( loc.GetStartLine(), fmt::fg(fmt::color::wheat)), fmt::styled(loc.GetStartColumn(), fmt::fg(fmt::color::wheat)));
#else
            fmt::print( "debug info: file \"{}\"\nin line {}, column {}:\n", loc.GetFileName(), loc.GetStartLine(), loc.GetStartColumn() );
#endif
            if( not loc.GetSource().empty() ) {
                std::string const &src = loc.GetSource();
                // try to mark the "marked" position
#if TEASCRIPT_USE_FMT_COLOR
                if( loc.GetStartLine() == loc.GetEndLine() && loc.GetEndColumn() > loc.GetStartColumn() ) {
                    fmt::print( fmt::fg( fmt::color::white_smoke ), "{}", src.substr( 0, loc.GetStartColumn() - 1 ) );
                    fmt::print( fmt::fg( fmt::color::violet ), "{}", src.substr( loc.GetStartColumn() - 1, loc.GetEndColumn() - loc.GetStartColumn() + 1 ) );
                    fmt::print( fmt::fg( fmt::color::white_smoke ), "{}\n", static_cast<size_t>(loc.GetEndColumn()) >= src.size() ? "" : src.substr( loc.GetEndColumn() ) );
                } else {
                    fmt::print( fmt::fg( fmt::color::white_smoke ), "{}\n", src );
                }
                fmt::print( fmt::fg( fmt::color::violet ), "{0:>{1}}\n", "^^^^^", loc.GetStartColumn() + 4LL );
#else
                fmt::print( "{}\n", src );
                fmt::print( "{0:>{1}}\n", "^^^^^", loc.GetStartColumn() + 4LL );
#endif
            }
        } else {
            throw std::runtime_error( "No debug info present!" );
        }

    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not show debug info! {}\n", ex.what() );
    }
}

void show_current_callstack( PartialParseState &state )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to show. Load a file first!" );
        }
        if( not state.program ) {
            throw std::runtime_error( "Nothing to show. Compile a program first!" );
        }
        if( not state.machine->IsSuspended() and not state.machine->IsErroneousHalted() ) {
            throw std::runtime_error( "Nothing to show. Program must be suspended or halted." );
        }

        fmt::println( "callstack of {}", state.machine->GetMainProgram()->GetName() );
        auto const &callstack = state.machine->GetCallStack();
        size_t n = callstack.size();
        std::string funcname;
        for( auto it = callstack.rbegin(); it != callstack.rend(); ++it, --n ) {
            funcname = it->name;
            if( it->func ) {
                funcname += it->func->ParameterInfoStr();
            } else if( not funcname.ends_with('>') ) {
                funcname += "(?)";
            }
#if TEASCRIPT_USE_FMT_COLOR
            fmt::print( "{:02}: ", fmt::styled( n, fmt::fg( fmt::color::light_sky_blue ) ) );
            fmt::print( fmt::fg( fmt::color::wheat ), "[ret: {:04}] ", it->ret );
            fmt::print( "{} of {:?}\n", fmt::styled( funcname, fmt::fg( fmt::color::white_smoke ) ), it->prog->GetName() );
#else
            fmt::print( "{:02}: [ret: {:04}] {} of {:?}\n", n - 1, it->ret, funcname, it->prog->GetName() );
#endif
        }
        if( callstack.empty() ) {
            fmt::println( "<empty>" );
        }

    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not show callstack! {}\n", ex.what() );
    }
}

void show_programcounter( PartialParseState &state )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to show. Load a file first!" );
        }
        if( not state.program ) {
            throw std::runtime_error( "Nothing to show. Compile a program first!" );
        }
        if( not state.machine->IsSuspended() and not state.machine->IsErroneousHalted() ) {
            throw std::runtime_error( "Nothing to show. Program must be suspended or halted." );
        }

        size_t const pc = state.machine->GetCurrentInstructionIndex();
        print_tsvm_instr( state.machine->GetCurrentProgram()->GetInstructions().at(pc), pc, false );

    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not show program counter! {}\n", ex.what() );
    }
}

void inspect_function( std::string const &rName, teascript::Context &rContext )
{
    try {
        teascript::ValueObject const  var = rContext.FindValueObject( rName );
        if( not var.GetTypeInfo()->IsSame<teascript::FunctionPtr>() ) {
            throw std::runtime_error( "Variable must be a function!" );
        }
        teascript::FunctionPtr const  func = var.GetValueCopy<teascript::FunctionPtr>();
        if( teascript::Func const *p_func_ast = dynamic_cast<teascript::Func const *>(func.get()) ) {
            p_func_ast->Apply( print_ast );
        } else if( teascript::CompiledFuncBase const *p_func_compiled = dynamic_cast<teascript::CompiledFuncBase const *>(func.get()) ) {
            auto const  prog  = p_func_compiled->GetProgram();
            auto const &instr = prog->GetInstructions();
            auto const  start = p_func_compiled->GetStartAddress();
            if( start > instr.size() ) {
                throw std::runtime_error( "Compiled Function is broken! (start > program size)" );
            }
            // searching JumpRel for determine end of function
            auto cur = start;
            auto end = start;
            while( cur > 0 ) {
                if( instr[cur].instr == teascript::eTSVM_Instr::JumpRel ) {
                    auto offset = instr[cur].payload.GetAsInteger();
                    if( cur + offset > instr.size() ) {
                        throw std::runtime_error( "Compiled Function is broken! (function > program size)" );
                    }
                    end = cur + offset - (start - cur);
                    break;
                }
                --cur;
            }
            PartialParseState state;
            state.filename = rName; // must fake sth.
            state.program = prog;
            disassemble_program( state, std::to_string( start ) + "," + std::to_string( end ) );

        } else {
            throw std::runtime_error( "Function is neither AST based nor a compiled function!\nBuilt-in C++ functions or User Callback Functions from C++ cannot be inspected!" );
        }

    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not inspect! {}\n", ex.what() );
    }   
}


// experimental, recursivley walking the AST and flatten it....
void build_flattened_ast( teascript::ASTNodePtr const &rNode, teascript::ASTNodeContainer &rFlattened )
{
    // is it a leaf?
    if( not rNode->HasChildren() ) {
        rFlattened.push_back( rNode );
    } else {
        // first the children...
        for( auto const &node : *rNode ) {
            build_flattened_ast( node, rFlattened );
        }
        // and our self at end. (always?)
        rFlattened.push_back( rNode );
    }
}


// experimental! builds flat ASTNode list and prints it.... (for debug and future development)
void print_flattened_ast( PartialParseState &state, teascript::Context &/*rContext*/ )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to flatten. Load a file first!" );
        }

        teascript::ASTNodeContainer  flat;

        for( auto const &p : state.parts ) {
            build_flattened_ast( p, flat );
        }

#if TEASCRIPT_USE_FMT_COLOR
        fmt::print( fmt::fg( fmt::color::wheat ), "FLAT AST:\n" );
#else
        fmt::print( "FLAT AST:\n" );
#endif

        long long c = 0;
        for( auto const &node : flat ) {
#if TEASCRIPT_USE_FMT_COLOR
            fmt::print( fmt::fg( fmt::color::wheat ), "{:04}: {}\n", ++c, fmt::styled( node->GetInfoStr(), fmt::fg( fmt::color::white_smoke ) ) );
#else
            fmt::print( "{:04}: {}\n", ++c, node->GetInfoStr() );
#endif
        }

    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not print flattened AST! {}\n", ex.what() );
    }
}


bool is_valid_core_config( unsigned int conf ) noexcept
{
    switch( conf & teascript::config::LevelMask ) {
    case teascript::config::LevelMinimal:
    case teascript::config::LevelCoreReduced:
    case teascript::config::LevelCore:
    case teascript::config::LevelUtil:
    case teascript::config::LevelFull:
        break;
    default:
        return false;
    }
    if( conf & 0xf0 ) { // this nibble is unused so far and must be 0.
        return false;
    }
    // Feature Opt-Out bits are handled lazily. It is allowed to set unknown bits. With this it is possible to disable everything just with 0xff'ff'ff'00
    return true;
}

teascript::config::eConfig parsecoreconfig( std::string const &s )
{
    if( not s.starts_with( "0x" ) ) {
        throw std::invalid_argument( "CoreLibrary config must be passed as hex string starting with 0x" );
    }
    unsigned int conf = static_cast<unsigned int>(std::stoul( s, nullptr, 16 ));
    if( is_valid_core_config( conf ) ) {
        return static_cast<teascript::config::eConfig>(conf);
    }
    throw std::invalid_argument( "CoreLibrary config is invalid!" );
}


// possible results of internal command dispatching
enum class CmdDispatched
{
    Unknown = 0,
    Dispatched,
    Exit
};

// internal command dispatch function.
CmdDispatched dispatch_cmd( std::string const &tmp, teascript::Parser &p, teascript::Context &context, PartialParseState &partial_state, Config &rConf, bool &batch_echo )
{
    if( tmp == ":debug" ) {
        rConf.debug = not rConf.debug;
        p.SetDebug( rConf.debug );
        context.is_debug = rConf.debug;
        partial_state.parser.SetDebug( rConf.debug );
        if( batch_echo ) {
            fmt::print( "<<< debug mode={}.\n", rConf.debug );
        }
        return CmdDispatched::Dispatched;
    } else if( tmp == ":debug ast" ) {
        partial_state.print_ast = not partial_state.print_ast;
        if( batch_echo ) {
            fmt::print( "<<< print ast={}.\n", partial_state.print_ast );
        }
        return CmdDispatched::Dispatched;
    } else if( tmp == ":debug tsvm" ) {
        partial_state.print_tsvm = not partial_state.print_tsvm;
        if( partial_state.print_tsvm ) {
            partial_state.machine->SetCurrentInstrCallback( print_tsvm_instr );
        } else {
            partial_state.machine->SetCurrentInstrCallback( teascript::StackVM::CurrentInstrCallback{} );
        }
        if( batch_echo ) {
            fmt::print( "<<< print tsvm={}.\n", partial_state.print_tsvm );
        }
        return CmdDispatched::Dispatched;
    } else if( tmp == ":silent" ) {
        rConf.silent = not rConf.silent;
        if( batch_echo ) {
            fmt::print( "<<< silent={}.\n", rConf.silent );
        }
        return CmdDispatched::Dispatched;
    } else if( tmp == ":reset" ) {
        p.ClearState();
        reload( partial_state );
        teascript::CoreLibrary().Bootstrap( context, teascript::config::LevelFull, rConf.no_compile );
#if TEASCRIPT_ENGINE_USE_WEB_PREVIEW
        teascript::WebPreviewModule().Load( context, teascript::config::LevelFull, rConf.no_compile );
#endif
        if( batch_echo ) {
            fmt::print( "<<< state reset. ok.\n" );
        }
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":reset=" ) ) {
        teascript::config::eConfig conf = teascript::config::LevelFull;
        try {
            conf = parsecoreconfig( tmp.substr( strlen( ":reset=" ) ) );
        } catch( std::exception const &ex ) {
            fmt::print( "<<< Cannot parse :reset= {}\n", ex.what() );
            return CmdDispatched::Dispatched;
        }
        p.ClearState();
        reload( partial_state );
        teascript::CoreLibrary().Bootstrap( context, conf, rConf.no_compile );
#if TEASCRIPT_ENGINE_USE_WEB_PREVIEW
        teascript::WebPreviewModule().Load( context, conf, rConf.no_compile );
#endif
        if( batch_echo ) {
            fmt::print( "<<< state reset. ok.\n" );
        }
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":load=" ) ) {
        load_file( tmp.substr( strlen( ":load=" ) ), partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":show " ) ) {
        show_lines( tmp.substr( strlen( ":show " ) ), partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":goto " ) ) {
        goto_pos( tmp.substr( strlen( ":goto " ) ), partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":stmt " ) ) {
        show_stmt( tmp.substr( strlen( ":stmt " ) ), partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":parse " ) ) {
        parse_lines( tmp.substr( strlen( ":parse " ) ), partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":eval " ) ) {
        eval_stmts( tmp.substr( strlen( ":eval " ) ), partial_state, context, rConf.silent );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":flatten" ) ) {
        print_flattened_ast( partial_state, context );
        return CmdDispatched::Dispatched;
    } else if( tmp == ":compile" ) {
        compile_ast_to_tsvm( partial_state, context );
        return CmdDispatched::Dispatched;
    } else if( tmp == ":exec" || tmp == ":x" ) {
        exec_tsvm( partial_state, context );
        return CmdDispatched::Dispatched;
    } else if( tmp == ":continue" || tmp == ":c" ) {
        continue_tsvm( partial_state, context );
        return CmdDispatched::Dispatched;
    } else if( tmp == ":disasm" ) {
        disassemble_program( partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":disasm ") ) {
        disassemble_program( partial_state, tmp.substr( strlen( ":disasm " ) ) );
        return CmdDispatched::Dispatched;
    } else if( tmp == ":dbinfo" ) {
        show_current_debuginfo( partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":dbinfo " ) ) {
        show_current_debuginfo( partial_state, tmp.substr( strlen( ":dbinfo " ) ) );
        return CmdDispatched::Dispatched;
    } else if( tmp == ":callstack" ) {
        show_current_callstack( partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp == ":pc" ) {
        show_programcounter( partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":constraint " ) ) {
        if( tmp.find( " none" ) != std::string::npos ) {
            partial_state.constraints = teascript::StackVM::Constraints::None();
            if( batch_echo ) {
                fmt::print( "<<< constraint set to 'None'. ok.\n" );
            }
            return CmdDispatched::Dispatched;
        } else {
            std::string const c = tmp.substr( strlen( ":constraint " ) );
            bool const timed = c.ends_with( "ms" );
            try {
                auto n = std::stoull( c );
                if( timed ) {
                    if( n > static_cast<unsigned long long>(std::numeric_limits<long long>::max()) ) {
                        throw std::invalid_argument( "number too big for milliseconds constraint!" );
                    }
                    partial_state.constraints = teascript::StackVM::Constraints::MaxTime( std::chrono::milliseconds( static_cast<long long>(n) ) );
                    if( batch_echo ) {
                        fmt::print( "<<< constraint set to 'Max Time'. ok.\n" );
                    }
                    return CmdDispatched::Dispatched;
                } else {
                    partial_state.constraints = teascript::StackVM::Constraints::MaxInstructions( n );
                    if( batch_echo ) {
                        fmt::print( "<<< constraint set to 'Max Instructions'. ok.\n" );
                    }
                    return CmdDispatched::Dispatched;
                }
            } catch( std::exception const &ex ) {
                fmt::print( "ERROR: {}\n", ex.what() );
            }
        }
    } else if( tmp == ":ls vars" ) {
        context.Dump();
        fmt::print( "\n" );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":search ") ) {
        context.Dump( tmp.substr( strlen( ":search " ) ) );
        fmt::print( "\n" );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":inspect " ) ) {
        inspect_function( tmp.substr( strlen( ":inspect " ) ), context );
        return CmdDispatched::Dispatched;
    } else if( tmp == ":?" || tmp == ":help" ) {
        show_help();
        fmt::print( "\n" );
        return CmdDispatched::Dispatched;
    } else if( tmp == ":q" || tmp == ":quit" || tmp == ":exit" ) {
        fmt::print( "<<< bye.\n" );
        return CmdDispatched::Exit;
    }
    return CmdDispatched::Unknown;
}


bool teascript_readline( char const *prompt, std::string &tmp, std::ifstream * const pBatch = nullptr, bool * const pBatchEcho = nullptr )
{
    if( pBatch ) {
        // as long as we have more lines in a possible batch file, use them.
        if( std::getline( *pBatch, tmp ) ) {
            if( pBatchEcho ) {
                if( tmp.starts_with( "#echo off" ) ) { // only recognized in batch mode.
                    *pBatchEcho = false;
                }
            }
            if( pBatchEcho == nullptr || *pBatchEcho ) {
                fmt::print( "{}{}\n", prompt, tmp );
            }
            if( pBatchEcho ) {
                if( tmp.starts_with( "#echo on" ) ) { // only recognized in batch mode.
                    *pBatchEcho = true;
                }
            }
            return true;
        }

    }
#if TEASCRIPT_HAS_READLINE
    char *input = readline( prompt );
    if( not input ) {
        return false;
    }
    add_history( input ); // TODO: filter empty lines.
    tmp = std::string( input );
    return true;
#else
    fmt::print( "{}", prompt );
    bool const res = static_cast<bool>(std::getline( std::cin, tmp ));
    if( tmp.ends_with( '\r' ) ) { // temporary fix on Windows with injected streambuf in std::cin
        tmp.erase( tmp.size() - 1 );
    }
    return res;
#endif
}

void launch_interactiveshell_internal( teascript::Context &context, teascript::Parser &p, PartialParseState &partial_state, Config &rConf, std::ifstream *const pBatch = nullptr )
{
#if TEASCRIPT_USE_FMT_COLOR
    fmt::print( fmt::fg( fmt::color::wheat ), "\nWelcome to the interactive shell of {}, version {}.{}.{}!\n",
                teascript::self_name_str(), teascript::version::get_major(), teascript::version::get_minor(), teascript::version::get_patch() );
    fmt::print( fmt::emphasis::italic, "{}\n\n", copyright_info() );
    fmt::print( "For multi-line inputs the line must end with a \\ (backslash).\n"
                "An ongoing multi-line input will use the prompt \\>  instead of >>> .\n"
                "Type :? <enter> or :help <enter> for show a list of internal commands.\n"
                "Press Ctrl+C or type :q <enter> for quit the program.\n\n" );
#else
    fmt::print( "\nWelcome to the interactive shell of {}, version {}.{}.{}!\n"
                "{}\n\n"
                "For multi-line inputs the line must end with a \\ (backslash).\n"
                "An ongoing multi-line input will use the prompt \\>  instead of >>> .\n"
                "Type :? <enter> or :help <enter> for show a list of internal commands.\n"
                "Press Ctrl+C or type :q <enter> for quit the program.\n\n", teascript::self_name_str(),
                teascript::version::get_major(), teascript::version::get_minor(), teascript::version::get_patch(),
                copyright_info() );
#endif

#if TEASCRIPT_HAS_READLINE
#if TEASCRIPT_USE_FMT_COLOR
    fmt::print( fmt::emphasis::italic, "Info: Using GNU Readline Library.\n\n" );
#else
    fmt::print( "Info: Using GNU Readline Library.\n\n" );
#endif
#endif

    bool batch_echo = true;
    std::string tmp;
    std::string content;
    char const *prompt = ">>> ";
    while( teascript_readline( prompt, tmp, pBatch, &batch_echo ) ) {
        if( tmp.ends_with( '\\' ) ) { // multi line input
            tmp.back() = '\n';
            content += tmp;
            prompt = "\\>  ";
            continue;
        }
        prompt = ">>> ";
        // first check for internal commands (starting with a colon : and at least one letter (or spelcial case :?) )
        if( tmp == ":" || tmp == ":?" || (tmp.starts_with(":") && tmp.size() > 1 && std::isalpha(static_cast<unsigned char>(tmp.at(1)))) ) {
            switch( dispatch_cmd( tmp, p, context, partial_state, rConf, batch_echo ) ) {
            case CmdDispatched::Unknown:
                fmt::print( "<<< Unknown internal command.\n    Type :? <enter> or :help <enter> for show a list of internal commands.\n" );
                [[fallthrough]];
            case CmdDispatched::Dispatched:
                continue;
            case CmdDispatched::Exit:
                return;
            }
        }

        // normal dispatching as TeaScript code...
        content += tmp + '\n';

        try {
            auto ast = p.Parse( content );
            if( ast != nullptr ) {
                if( partial_state.print_ast ) {
#if TEASCRIPT_USE_FMT_COLOR
                    fmt::print( fmt::fg( fmt::color::wheat ), "AST:\n" );
#else
                    fmt::print( "AST:\n" );
#endif
                    ast->Apply( print_ast );
                }
                auto const result = ast->Eval( context );
                if( not rConf.silent && batch_echo && result.HasPrintableValue() ) {
#if TEASCRIPT_USE_FMT_COLOR
                    fmt::print( "<<< {}", fmt::styled( result.PrintValue(), fmt::fg( fmt::color::light_sky_blue ) ) );
#else
                    fmt::print( "<<< {}", result.PrintValue() );
#endif
                } else if( batch_echo ) {
                    fmt::print( "<<< ok." );
                }
            } else {
                fmt::print( "Internal Error! Parser returned nullptr!" );
                p.ClearState();
            }
        } catch( teascript::exception::runtime_error const &ex ) {
            fmt::print( "<<< " );
#if TEASCRIPT_USE_FMT_COLOR
            teascript::util::pretty_print_colored( ex );
#else
            teascript::util::pretty_print( ex );
#endif
            p.ClearState();
        } catch( std::exception const &ex ) {
            fmt::print( "<<< {}", ex.what() );
            p.ClearState();
        }
        content.clear();
        if( batch_echo ) {
            fmt::print( "\n" );
        }
    }
}


void launch_interactiveshell( Config &rConf )
{
    teascript::Dialect  d;
    teascript::Parser   p;
    teascript::Context  c;
    d.parameters_are_default_const = not rConf.old_mutable_parameters;
    d.parameters_are_default_shared = not rConf.old_deepcopy_parameters;
    c.dialect = d;
    p.OverwriteDialect( d );
    if( rConf.debug ) {
        p.SetDebug( rConf.debug );
        c.is_debug = rConf.debug;
    }
    teascript::CoreLibrary().Bootstrap( c, rConf.core_config, rConf.no_compile );
#if TEASCRIPT_ENGINE_USE_WEB_PREVIEW
    teascript::WebPreviewModule().Load( c, rConf.core_config, rConf.no_compile );
#endif
    PartialParseState  partial_state;

    partial_state.parser.OverwriteDialect( c.dialect ); // use eventually modified dialect.
    partial_state.parser.SetDebug( c.is_debug );
    partial_state.machine = std::make_shared< teascript::StackVM::Machine<false> >();

    std::ifstream  batch( rConf.filename );

    if( not rConf.no_logo ) {
        print_logo();
    }

    launch_interactiveshell_internal( c, p, partial_state, rConf, rConf.filename.empty() ? nullptr : &batch);
}

void launch_interactiveshell_after_exec( teascript::Context &context, teascript::Parser &p, PartialParseState &partial_state, Config &rConf )
{
    std::string const text = partial_state.machine->IsSuspended() ? "suspended" : partial_state.machine->IsErroneousHalted() ? "halted" : "executed";
    fmt::print( "\n\nScript {}, starting interactive shell...\n", text );

    partial_state.program = partial_state.machine->GetMainProgram();

    launch_interactiveshell_internal( context, p, partial_state, rConf );
}


Config parse_parameters( std::vector<std::string> const &args )
{
    Config config;
    bool argmode = false;
    bool parameter_end = false;
    bool compile_set = false;
    Config::Mode tmp = Config::Mode::Unknown;
    if( args.size() < 2 ) {
        config.mode = Config::Mode::Shell;
        config.no_compile = true;
    }
    for( size_t i = 1; i < args.size(); ++i ) {
        if( argmode ) {
            config.script_args.emplace_back( args[i] );
        } else if( (args[i][0] != '-' && 0 != args[i].compare( "/?" )) || parameter_end ) {
            config.filename = args[i];
            argmode = true;
        } else if( 0 == args[i].compare( "--" ) ) {
            parameter_end = true; // next is filename, for can have filename with starting dash.
        } else {
            if( 0 == args[i].compare( "-D" ) || 0 == args[i].compare( "--debug" ) ) {
                config.debug = true;
                config.opt_level = teascript::eOptimize::Debug;
            } else if( 0 == args[i].compare( "-O0" ) ) {
                config.opt_level = teascript::eOptimize::O0;
                config.debug = false;
            } else if( 0 == args[i].compare( "-O1" ) ) {
                config.opt_level = teascript::eOptimize::O1;
                config.debug = false;
            } else if( 0 == args[i].compare( "-O2" ) ) {
                config.opt_level = teascript::eOptimize::O2;
                config.debug = false;
            } else if( 0 == args[i].compare( "-T" ) || 0 == args[i].compare( "--time" ) ) {
                config.time_measure = true;
            } else if( 0 == args[i].compare( "--printAST" ) ) {
                tmp = Config::Mode::Parse;
            } else if( 0 == args[i].compare( "--printTSVM" ) ) {
                tmp = Config::Mode::PrintCompile;
            } else if( args[i].starts_with( "--config=" ) ) {
                try {
                    config.core_config = parsecoreconfig( args[i].substr( strlen( "--config=" ) ) );
                } catch( std::exception const &ex ) { // bail out
                    fmt::print( "ERROR: Cannot parse --config param:\n{}\n", ex.what() );
                    config.mode = Config::Mode::Error;
                    break;
                }
            } else if( 0 == args[i].compare( "-i" ) || 0 == args[i].compare( "--interactive" ) ) {
                config.interactive = true;
            } else if( 0 == args[i].compare( "--silent" ) ) {
                config.silent = true;
            } else if( 0 == args[i].compare( "--eval" ) || 0 == args[i].compare( "--no-compile" ) ) { // explicit set to eval (for Exec mdoe)
                config.no_compile = true;
                compile_set = true;
            } else if( 0 == args[i].compare( "--no-eval" ) ) { // explicit set to compile (for Shell mode)
                config.no_compile = false;
                compile_set = true;
            } else if( 0 == args[i].compare( "--batch" ) || 0 == args[i].compare( "--shell" ) ) { // explicit activate Shell mode. (optional filename will be used as batch file)
                tmp = Config::Mode::Shell;
                if( not compile_set ) {
                    // shell has eval as default!
                    config.no_compile = true;
                }
            } else if( 0 == args[i].compare( "--exec" ) ) { // explicit activate Exec mode.
                tmp = Config::Mode::Exec;
            } else if( 0 == args[i].compare( "--compile" ) ) {
                tmp = Config::Mode::Compile;
            } else if( 0 == args[i].compare( "--info" ) ) {
                tmp = Config::Mode::PrintInfo;
            /*} else if( 0 == args[i].compare( "--old-mutable-parameters" ) ) {
                config.old_mutable_parameters = true;*/
            } else if( 0 == args[i].compare( "--old-deepcopy-parameters" ) ) {
                config.old_deepcopy_parameters = true;
            } else if( 0 == args[i].compare( "--legacy-args" ) ) {
                config.legacy_args = true;
            } else if( 0 == args[i].compare( "--no-logo" ) ) {
                config.no_logo = true;
            } else if( 0 == args[i].compare( "-v" ) || 0 == args[i].compare( "--version" ) ) {
                tmp = Config::Mode::PrintVersion;
            } else if( 0 == args[i].compare( "-?" ) || 0 == args[i].compare( "-h" ) || 0 == args[i].compare( "/?" ) || 0 == args[i].compare( "--help" ) ) {
                tmp = Config::Mode::PrintParameters;
            } else {
                fmt::print( "ERROR: Unknown parameter: {}\n", args[i] );
                config.mode = Config::Mode::Error;
                break; // unknown parameter! bail out!
            }
        }

        if( argmode && config.mode == Config::Mode::Unknown ) {
            config.mode = Config::Mode::Exec;
        } else if( tmp != Config::Mode::Unknown ) {
            if( config.mode == Config::Mode::Unknown ) {
                config.mode = tmp;
                tmp = Config::Mode::Unknown;
            } else { // cannot have more than one mode! bail out!
                config.mode = Config::Mode::Error;
                break;
            }
        }
    }
    if( config.mode == Config::Mode::Unknown ) { // if no explicit mode was given, check for implicit Shell.
        if( config.interactive || compile_set /*--no-eval*/ || config.silent || config.no_logo ) {
            config.mode = Config::Mode::Shell;
            if( not compile_set ) {
                // shell has eval as default!
                config.no_compile = true;
            }
        }
    }
    return config;
}


void print_version( )
{
    fmt::print( "\n{}, version {}.{}.{}.\n"
                "{}\n\n", teascript::self_name_str(),
                teascript::version::get_major(), teascript::version::get_minor(), teascript::version::get_patch(),
                copyright_info() );
}

void use_help( char const *s )
{
    fmt::print( "{}:\nUse -? or --help or /? for show a help message with valid parameters.\n", s );
}


void print_time( auto const t0, auto const t1, auto const t2, auto const t3, auto const t4 )
{
    if( t2 == t1 ) { // no parsing --> load and exec .tsb
        fmt::print( "\nElapsed time:\nTotal {:.8}, Bootstrap {:.8}, Loading Program {:.8}, Eval/Exec {:.8}\n",
                    std::chrono::duration<double>( t4 - t0 ),
                    std::chrono::duration<double>( t1 - t0 ),
                    //std::chrono::duration<double>( t2 - t1 ),
                    std::chrono::duration<double>( t3 - t2 ),
                    std::chrono::duration<double>( t4 - t3 ) );
    } else {
        fmt::print( "\nElapsed time:\nTotal {:.8}, Bootstrap {:.8}, Parsing {:.8}, Compile {:.8}, Eval/Exec {:.8}\n",
                    std::chrono::duration<double>( t4 - t0 ),
                    std::chrono::duration<double>( t1 - t0 ),
                    std::chrono::duration<double>( t2 - t1 ),
                    std::chrono::duration<double>( t3 - t2 ),
                    std::chrono::duration<double>( t4 - t3 ) );
    }
}


int execute_teascript_binary( Config &config, std::filesystem::path const &rAbsFile )
{
    teascript::Dialect  d;
    teascript::Parser   p; // for the case shell is launched (interactive mode)
    teascript::Context  c;

    PartialParseState  partial_state;  // for the case shell is launched (interactive mode)

    try {
        // build utf-8 filename again... *grrr*
        std::string const filename_u8 = teascript::util::utf8_path_to_str( rAbsFile );

        // we optimize for the good case. So, we assume the file is valid and we need the whole content.
        std::ifstream file( rAbsFile, std::ios::binary | std::ios::ate ); // ate jumps to end.
        if( not file ) {
            throw std::runtime_error( "Cannot load file! " + filename_u8 );
        }
        auto size = file.tellg();
        file.seekg( 0 );
        std::vector<unsigned char> content( static_cast<size_t>(size) );
        file.read( reinterpret_cast<char*>(content.data()), size );

        // check header loading and version.
        teascript::StackVM::ProgramPtr prog_header = teascript::StackVM::Program::Load( std::span( content.data(), content.size() ), true );

        if( prog_header->GetCompilerVersion() != teascript::version::combined_number() ) {
            unsigned int const v = prog_header->GetCompilerVersion();
            fmt::print( "TeaScript binary was compiled with a different compiler version {}.{}.{}\n",
                        TEASCRIPT_VERSION_EXTRACT_MAJOR( v ),
                        TEASCRIPT_VERSION_EXTRACT_MINOR( v ),
                        TEASCRIPT_VERSION_EXTRACT_PATCH( v ) );
            throw std::runtime_error( "Cannot execute binary from another version!" );
        }

        // start time measuring for bootstrap
        auto const  t0 = std::chrono::steady_clock::now();
        
        d.parameters_are_default_const = not config.old_mutable_parameters;
        d.parameters_are_default_shared = not config.old_deepcopy_parameters;
        c.dialect = d;
        p.OverwriteDialect( d );
        if( config.debug ) {
            p.SetDebug( config.debug );
            c.is_debug = config.debug;
        }
        auto  machine = std::make_shared<teascript::StackVM::Machine<false>>();
        // bootstrapping the core library into context.
        teascript::CoreLibrary().Bootstrap( c, config.core_config, config.no_compile );
#if TEASCRIPT_ENGINE_USE_WEB_PREVIEW
        teascript::WebPreviewModule().Load( c, config.core_config, config.no_compile );
#endif
        // set script arguments.
        if( not config.script_args.empty() ) {
            c.SetScriptArgs( config.script_args, config.legacy_args );
        }

        if( config.interactive ) {
            partial_state.filename = filename_u8;
            partial_state.machine = machine;
            partial_state.parser.OverwriteDialect( c.dialect ); // use eventually modified dialect.
            partial_state.parser.SetDebug( c.is_debug );
        }

        auto const  t1 = std::chrono::steady_clock::now();
        auto        t2 = t1;
        auto        t3 = t2;
        auto        t4 = t3;

        teascript::StackVM::ProgramPtr  program = teascript::StackVM::Program::Load( std::span( content.data(), content.size() ), false );
        t3 = std::chrono::steady_clock::now();

        machine->Exec( program, c );
        if( machine->IsSuspended() && not config.interactive ) {
            while( machine->IsSuspended() ) { // auto continue!
                machine->Continue( c );
            }
        }
        t4 = std::chrono::steady_clock::now();

        machine->ThrowPossibleErrorException();

        teascript::ValueObject  result;
        if( machine->HasResult() ) {
            result = machine->MoveResult();
        }
        if( result.HasPrintableValue() ) {
            fmt::print( "{}", result.PrintValue() ); //NOTE: Intentionally always uncolored for easier parsing
        }
        if( config.time_measure ) {
            print_time( t0, t1, t2, t3, t4 );
    #if TEASCRIPT_ENABLE_STACKVM_INSTR_TIMES
            if( not config.no_compile ) {
                machine->DumpInstrTimes( 0.00000010 );
            }
    #endif
        }
        if( config.interactive ) {
            launch_interactiveshell_after_exec( c, p, partial_state, config );
        } else {
            return EXIT_SUCCESS;
        }

    } catch( teascript::exception::runtime_error const &ex ) {
#if TEASCRIPT_USE_FMT_COLOR
        fmt::print( fmt::fg( fmt::color::tomato ), "ERROR:\n" );
        teascript::util::pretty_print_colored( ex );
#else
        fmt::print( "ERROR:\n" );
        teascript::util::pretty_print( ex );
#endif
        if( config.interactive ) {
            launch_interactiveshell_after_exec( c, p, partial_state, config );
        } else {
            return EXIT_FAILURE;
        }
    } catch( std::exception const &ex ) {
        fmt::print( "ERROR: {}\n", ex.what() );
        if( config.interactive ) {
            launch_interactiveshell_after_exec( c, p, partial_state, config );
        } else {
            return EXIT_FAILURE;
        }
    }
    return EXIT_SUCCESS;
}


int main_teascript_exe( std::vector<std::string> const &args )
{
    Config  config = parse_parameters( args );
    if( Config::Mode::Shell == config.mode ) {
#ifdef _WIN32    
        signal( SIGINT, SIG_IGN ); // to remove spurious exit code -<very big number> when stop std::getline with Ctrl+C
#else //FIXME: check on Linux!
        //set_signal_handler();
#endif
        try {
            launch_interactiveshell( config );
        } catch( teascript::exception::runtime_error const &ex ) { // on early loading stages / bootsrapping there might escape an exception to here.
#if TEASCRIPT_USE_FMT_COLOR
            fmt::print( fmt::fg( fmt::color::tomato ), "ERROR:\n" );
            teascript::util::pretty_print_colored( ex );
#else
            fmt::print( "ERROR:\n" );
            teascript::util::pretty_print( ex );
#endif
            return EXIT_FAILURE;
        } catch( std::exception const &ex ) {
            fmt::print( "ERROR: {}\n", ex.what() );
            return EXIT_FAILURE;
        }
    } else if( Config::Mode::Exec == config.mode ) {
        // NOTE: strings from config are UTF-8 here already!
        // TODO: apply include paths before try absolute()
        std::filesystem::path const script( std::filesystem::absolute( teascript::util::utf8_path( config.filename ) ) );
        if( script.extension() == ".tsb" ) { // compiled binary?
            return execute_teascript_binary( config, script );
        } // else: try to interpret as TeaScript code.
        std::ifstream file( script, std::ios::binary | std::ios::ate ); // ate jumps to end.
        if( file ) {
            auto size = file.tellg();
            file.seekg( 0 );
            std::vector<char> buf( static_cast<size_t>(size) + 1 ); // ensure zero terminating!
            file.read( buf.data(), size );

            // build utf-8 filename again... *grrr*
            std::string const filename_u8 = teascript::util::utf8_path_to_str( script );

            PartialParseState  partial_state;  // for the case shell is launched (interactive mode)

            // start time measuring for bootstrap
            auto const  t0 = std::chrono::steady_clock::now();

            teascript::Dialect  d;
            teascript::Parser   p;
            teascript::Context  c;
            d.parameters_are_default_const = not config.old_mutable_parameters;
            d.parameters_are_default_shared = not config.old_deepcopy_parameters;
            c.dialect = d;
            p.OverwriteDialect( d );
            if( config.debug ) {
                p.SetDebug( config.debug );
                c.is_debug = config.debug;
            }
            teascript::StackVM::Compiler        compiler;
            auto  machine = std::make_shared<teascript::StackVM::Machine<false> >();
            // bootstrapping the core library into context.
            teascript::CoreLibrary().Bootstrap( c, config.core_config, config.no_compile );
#if TEASCRIPT_ENGINE_USE_WEB_PREVIEW
            teascript::WebPreviewModule().Load( c, config.core_config, config.no_compile );
#endif
            // set script arguments.
            if( not config.script_args.empty() ) {
                c.SetScriptArgs( config.script_args, config.legacy_args );
            }

            teascript::Content  content{buf.data(), buf.size()};

            if( config.interactive ) {
                partial_state.filename = filename_u8;
                partial_state.storage = std::string( buf.begin(), buf.end() );
                partial_state.content = content;
                partial_state.machine = machine;
                partial_state.parser.OverwriteDialect( c.dialect ); // use eventually modified dialect.
                partial_state.parser.SetDebug( c.is_debug );
            }

            auto const  t1 = std::chrono::steady_clock::now();
            auto        t2 = t1;
            auto        t3 = t2;

            try {
                auto  ast = p.Parse( content, filename_u8 ); // TODO: absolute path or filename?
                t2 = std::chrono::steady_clock::now();
                t3 = t2;
                auto t4 = t3;
                teascript::ValueObject  result;
                if( config.no_compile ) {
                    result = ast->Eval( c );
                    t4 = std::chrono::steady_clock::now();
                } else {
                    auto program = compiler.Compile( ast, config.opt_level );
                    t3 = std::chrono::steady_clock::now();
                    machine->Exec( program, c );
                    if( machine->IsSuspended() && not config.interactive ) {
                        while( machine->IsSuspended() ) { // auto continue!
                            machine->Continue( c );
                        }
                    }
                    t4 = std::chrono::steady_clock::now();

                    machine->ThrowPossibleErrorException();
                    if( machine->HasResult() ) {
                        result = machine->MoveResult();
                    }
                }
                if( result.HasPrintableValue() ) {
                    fmt::print( "{}", result.PrintValue() ); //NOTE: Intentionally always uncolored for easier parsing
                }
                if( config.time_measure ) {
                    print_time( t0, t1, t2, t3, t4 );
#if TEASCRIPT_ENABLE_STACKVM_INSTR_TIMES
                    if( not config.no_compile ) {
                        machine->DumpInstrTimes( 0.00000010 );
                    }
#endif
                }
                if( config.interactive ) {
                    launch_interactiveshell_after_exec( c, p, partial_state, config );
                } else {
                    return EXIT_SUCCESS;
                }
            } catch( teascript::exception::runtime_error const &ex ) {
                std::string  src_overwrite;
                // no context string but we have location? try to extract from source...
                if( ex.GetContextStr().empty() && ex.IsSourceLocSet() && ex.GetFileStr() == filename_u8 ) {
                    try {
                        content.GoTo( ex.GetLine(), ex.GetColumn() );
                        src_overwrite = teascript::util::extract_current_line( content );
                    } catch( ... ) {
                    }
                }
#if TEASCRIPT_USE_FMT_COLOR
                fmt::print( fmt::fg( fmt::color::tomato ), "ERROR:\n" );
                teascript::util::pretty_print_colored( ex, std::move( src_overwrite ) );
#else
                fmt::print( "ERROR:\n" );
                teascript::util::pretty_print( ex, std::move( src_overwrite ) );
#endif
                if( config.interactive ) {
                    launch_interactiveshell_after_exec( c, p, partial_state, config );
                } else {
                    return EXIT_FAILURE;
                }
            } catch( std::exception const &ex ) {
                fmt::print( "ERROR: {}\n", ex.what() );
                if( config.interactive ) {
                    launch_interactiveshell_after_exec( c, p, partial_state, config );
                } else {
                    return EXIT_FAILURE;
                }
            }
        } else {
            fmt::print( "ERROR: cannot open file {}\n", script.string() );
            return EXIT_FAILURE;
        }
    } else if( Config::Mode::Parse == config.mode ) {
        // NOTE: strings from config are UTF-8 here already!
        // TODO: apply include paths before try absolute()
        std::filesystem::path const script( std::filesystem::absolute( teascript::util::utf8_path( config.filename ) ) );
        std::ifstream file( script, std::ios::binary | std::ios::ate ); // ate jumps to end.
        if( file ) {
            auto size = file.tellg();
            file.seekg( 0 );
            std::vector<char> buf( static_cast<size_t>(size) + 1 ); // ensure zero terminating!
            file.read( buf.data(), size );

            // build utf-8 filename again... *grrr*
            std::string const filename_u8 = teascript::util::utf8_path_to_str( script );

            // we dont need a context here, just parse...
            teascript::Parser  p;
            p.SetDebug( config.debug );
            try {
                auto const  start = std::chrono::steady_clock::now();
                auto ast = p.Parse( {buf.data(), buf.size()}, filename_u8 );
                auto const  end = std::chrono::steady_clock::now();
                if( ast != nullptr ) {
#if TEASCRIPT_USE_FMT_COLOR
                    fmt::print( fmt::fg( fmt::color::wheat ), "AST:\n" );
#else
                    fmt::print( "AST:\n" );
#endif
                    ast->Apply( print_ast );
                    fmt::print( "\nparsing time: {}\n", std::chrono::duration_cast<std::chrono::microseconds>(end - start) );
                } else {
                    fmt::print( "Internal Error! Parser returned nullptr!" );
                    return EXIT_FAILURE;
                }
            } catch( teascript::exception::runtime_error const &ex ) {
#if TEASCRIPT_USE_FMT_COLOR
                fmt::print( fmt::fg( fmt::color::tomato ), "ERROR:\n" );
                teascript::util::pretty_print_colored( ex );
#else
                fmt::print( "ERROR:\n" );
                teascript::util::pretty_print( ex );
#endif
                return EXIT_FAILURE;
            } catch( std::exception const &ex ) {
                fmt::print( "ERROR: {}\n", ex.what() );
                return EXIT_FAILURE;
            }
        } else {
            fmt::print( "ERROR: cannot open file {}\n", script.string() );
            return EXIT_FAILURE;
        }
    } else if( Config::Mode::PrintCompile == config.mode || Config::Mode::Compile == config.mode ) {
        // NOTE: strings from config are UTF-8 here already!
        // TODO: apply include paths before try absolute()
        std::filesystem::path const script( std::filesystem::absolute( teascript::util::utf8_path( config.filename ) ) );
        std::ifstream file( script, std::ios::binary | std::ios::ate ); // ate jumps to end.
        if( file ) {
            auto size = file.tellg();
            file.seekg( 0 );
            std::vector<char> buf( static_cast<size_t>(size) + 1 ); // ensure zero terminating!
            file.read( buf.data(), size );

            // build utf-8 filename again... *grrr*
            std::string const filename_u8 = teascript::util::utf8_path_to_str( script );

            // we dont need a context here, just parse and compile...
            teascript::Parser                   p;
            p.SetDebug( config.debug );
            teascript::StackVM::Compiler        compiler;
            try {
                auto const  start = std::chrono::steady_clock::now();
                teascript::StackVM::ProgramPtr  prog;
                if( script.extension() == ".tsb" ) {
                    if( Config::Mode::PrintCompile == config.mode ) {
                        prog = teascript::StackVM::Program::Load( script );
                    } else { // huh?
                        fmt::print( "File is a TeaScript binary already!" );
                        return EXIT_FAILURE;
                    }
                } else {
                    auto ast = p.Parse( {buf.data(), buf.size()}, filename_u8 );
                    prog = compiler.Compile( ast, config.opt_level );
                }
                auto const  end = std::chrono::steady_clock::now();
                if( prog != nullptr ) {
                    if( Config::Mode::PrintCompile == config.mode ) {
                        print_tsvm_code( prog->GetInstructions() );
                    } else {
                        auto new_file = script;
                        new_file += ".tsb";
                        if( not prog->Save( new_file ) ) {
                            fmt::print( "Could not save compiled program to {}", new_file.string() );
                            return EXIT_FAILURE;
                        }
                        fmt::print( "Saved program as {}", new_file.string() );
                    }
                    if( script.extension() == ".tsb" ) {
                        fmt::print( "\nload time: {}\n", std::chrono::duration_cast<std::chrono::microseconds>(end - start) );
                    } else {
                        fmt::print( "\ncompile time: {}\n", std::chrono::duration_cast<std::chrono::microseconds>(end - start) );
                    }
                } else {
                    fmt::print( "Internal Error! Compiler returned nullptr!" );
                    return EXIT_FAILURE;
                }
            } catch( teascript::exception::runtime_error const &ex ) {
#if TEASCRIPT_USE_FMT_COLOR
                fmt::print( fmt::fg( fmt::color::tomato ), "ERROR:\n" );
                teascript::util::pretty_print_colored( ex );
#else
                fmt::print( "ERROR:\n" );
                teascript::util::pretty_print( ex );
#endif
                return EXIT_FAILURE;
            } catch( std::exception const &ex ) {
                fmt::print( "ERROR: {}\n", ex.what() );
                return EXIT_FAILURE;
            }
        } else {
            fmt::print( "ERROR: cannot open file {}\n", script.string() );
            return EXIT_FAILURE;
        }
    } else if( Config::Mode::PrintInfo == config.mode ) {
        // NOTE: strings from config are UTF-8 here already!
        // TODO: apply include paths before try absolute()
        std::filesystem::path const script( std::filesystem::absolute( teascript::util::utf8_path( config.filename ) ) );
        if( script.extension() != ".tsb" ) {
            if( script.extension() == ".tea" ) {
                fmt::print( "A TeaScript source file. Must be compiled first.\n" );
            } else {
                fmt::print( "Unknown file type. Must be a TeaScript binary file (.tsb)\n" );
            }
            return EXIT_FAILURE;
        }
        try {
            auto const prog = teascript::StackVM::Program::Load( script, true );
            if( prog != nullptr ) {
                auto const v = prog->GetCompilerVersion();
                auto const opt_level = prog->GetUsedOptimization();
                auto const opt_levelstr = opt_level == teascript::eOptimize::Debug ? "Debug" : opt_level == teascript::eOptimize::O0 ? "O0"
                                        : opt_level == teascript::eOptimize::O1 ? "O1" : "O2";
                fmt::print( "Info:\n"
                            "Compiler Version   : {}.{}.{}\n"
                            "Optimization Level : {}\n"
                            "Name               : {}\n",
                            TEASCRIPT_VERSION_EXTRACT_MAJOR( v ),
                            TEASCRIPT_VERSION_EXTRACT_MINOR( v ),
                            TEASCRIPT_VERSION_EXTRACT_PATCH( v ),
                            opt_levelstr,
                            prog->GetName() );

            } else {
                fmt::print( "Internal Error! Compiler returned nullptr!" );
                return EXIT_FAILURE;
            }
        } catch( std::exception const &ex ) {
            fmt::print( "ERROR: {}\n", ex.what() );
            return EXIT_FAILURE;
        }

        return EXIT_SUCCESS;
    } else if( Config::Mode::PrintVersion == config.mode ) {
        print_version();
        return EXIT_SUCCESS;
    } else if( Config::Mode::PrintParameters == config.mode ) {
        print_parameters();
        return EXIT_SUCCESS;
    } else /*if( config.mode == Config::Mode::Unknown )*/ {
        use_help( args[0].c_str() );
        return EXIT_FAILURE;
    }

    puts( "END!" );

    return EXIT_SUCCESS;
}

std::string build_abs_exe_path( std::string const &argv1 )
{
    auto path = std::filesystem::absolute( teascript::util::utf8_path( argv1 ) );
#if defined(_WIN32)
    auto const tmp_u8 = path.generic_u8string();
    return std::string( tmp_u8.begin(), tmp_u8.end() ); // must make a copy ... :-(
#else
    (void)teascript::util::debug_print_currentline; // silence unused function warning.
    (void)teascript::util::pretty_print;            // silence unused function warning.
    (void)teascript::util::pretty_print_colored;    // silence unused function warning.
    return path.generic_string();
#endif
}

// On Windows we use wmain for get full Unicode support on the command line. 
// Convert UTF-16 args to UTF-8 and then call our main.
#if defined(_WIN32) && 1

std::string utf16toutf8( wchar_t const *str )
{
    int const  len = (int)::wcslen( str );
    int res = WideCharToMultiByte( CP_UTF8, 0, str, len, 0x0, 0, 0x0, 0x0 );
    if( res <= 0 ) {
        throw std::invalid_argument( "Cannot convert parameter to UTF-8. Please check proper encoding!" );
    }
    std::string  utf8;
    utf8.resize( res );
    res = WideCharToMultiByte( CP_UTF8, 0, str, len, utf8.data(), (int)utf8.size(), 0x0, 0x0 );
    if( res <= 0 ) {
        throw std::invalid_argument( "Cannot convert parameter to UTF-8. Please check proper encoding!" );
    }
    return utf8;
}

std::wstring utf8toutf16( char const *str, size_t const size )
{
    if( !str || !*str ) {
        return L"";
    }
    int const  len = (int)size;
    int res = MultiByteToWideChar( CP_UTF8, 0, str, len, 0x0, 0 );
    if( res <= 0 ) {
        return L"utf8toutf16() error!";
    }
    std::wstring  utf16;
    utf16.resize( res );
    res = MultiByteToWideChar( CP_UTF8, 0, str, len, utf16.data(), (int)utf16.size() );
    if( res <= 0 ) {
        return L"utf8toutf16() error!";
    }
    return utf16;
}


// the stream hooking was inspired from here:
// https://stackoverflow.com/questions/1660492/utf-8-output-on-windows-console
// NOTE: currently only the input hook is used. Output is handled by libfmt.

class Win32Console_OutStream_Utf8ToUtf16Buffer : public std::stringbuf
{
    HANDLE mHandle;
    std::string mBacklog;

    static size_t count_unfinished_utf8_bytes( const std::string &s )
    {
        constexpr unsigned char utf8_Continuation_Prefix = 0x80;  // 10xx xxxx
        constexpr unsigned char utf8_Continuation_Mask   = 0xC0;  // 1100 0000

        if( s.empty() ) {
            return 0;
        }
        auto rit = s.rbegin();
        unsigned char const last = *rit;
        if( last <= 0x7F ) { // good ol' ASCII ...
            return 0;
        }
        // assume we only have valid UTF-8, then everything greater the max continuation char is a leading char.
        if( last > 0xBF ) {
            return 1;
        }

        // normally this is covered by the above checks already...
        if( (last & utf8_Continuation_Mask) != utf8_Continuation_Prefix ) {
            return 0;
        }

        while( rit != s.rend() && (*rit & utf8_Continuation_Mask) != utf8_Continuation_Prefix ) {
            ++rit;
        }

        if( rit == s.rend() ) {
            return s.size();
        }

        unsigned char const  c = *rit;
        size_t        const  expected = c >= 0xF0 ? 3 : c >= 0xE0 ? 2 : c >= 0xC0 ? 1 : 0;
        size_t        const  actual   = std::distance( s.rbegin(), rit );
        if( actual == expected ) {
            return 0; // is complete!
        }

        return actual;
    }
    
public:
    Win32Console_OutStream_Utf8ToUtf16Buffer() 
        : mHandle( ::GetStdHandle( STD_OUTPUT_HANDLE ) )
    {}

    int sync() override
    {
        // use backlog with new data...
        std::string buffer = mBacklog + str();
        // count unfinished utf8 bytes
        size_t const  unfinished_utf8_bytes = count_unfinished_utf8_bytes( buffer );
        // copy them to backlog for next call.
        mBacklog = std::string( buffer.end() - unfinished_utf8_bytes, buffer.end() );

        // now we can clear the input.
        str( "" );

        // cut off the unfinisehed data.
        buffer.erase( buffer.size() - unfinished_utf8_bytes );
        if( buffer.empty() ) {
            return 0;
        }

        std::wstring const utf16 = utf8toutf16( buffer.c_str(), buffer.size() );
        DWORD written = 0;
        ::WriteConsoleW( mHandle, utf16.c_str(), static_cast<DWORD>( utf16.size() ), &written, 0x0 );

        return 0;
    }
 
#if 0
    Win32Console_OutStream_Utf8ToUtf16Buffer::int_type overflow( int_type c ) override
    {
        mBacklog += traits_type::to_char_type( c );
        return traits_type::not_eof( c );
    }
#endif
};

template< typename T > requires std::derived_from< T, std::streambuf >
class Win32Console_OutStream_Overwrite
{
public:
    Win32Console_OutStream_Overwrite()
        : mOld( nullptr )
    {
        if( ::GetFileType( ::GetStdHandle( STD_OUTPUT_HANDLE ) ) == FILE_TYPE_CHAR ) {
            std::cout.flush();
            mStreamBuf = std::make_unique<T>();
            mOld = std::cout.rdbuf();
            std::cout.rdbuf( mStreamBuf.get() );
        }
    }
    ~Win32Console_OutStream_Overwrite()
    {
        if( mOld ) {
            std::cout.flush();
            std::cout.rdbuf( mOld );
        }
    }
private:
    std::streambuf     *mOld;
    std::unique_ptr<T>  mStreamBuf;;
};

class Win32Console_InStream_Utf16ToUtf8Buffer : public std::streambuf
{
    std::string mBuffer;
    HANDLE      mHandle;
public:
    Win32Console_InStream_Utf16ToUtf8Buffer() 
        : mHandle( ::GetStdHandle( STD_INPUT_HANDLE ) )
    {
        setg( 0, 0, 0 );
    }
private:
    int_type underflow() override
    {
        if( gptr() >= egptr() ) {
            wchar_t  wbuf[128] = L"\0";
            DWORD read = 0;
            if( !::ReadConsoleW( mHandle, wbuf, ARRAYSIZE( wbuf ) - 2, &read, 0x0 ) ) {
                return traits_type::eof();
            }
            if( read > 0 && IS_HIGH_SURROGATE( wbuf[read - 1] ) ) {
                // try to read one more character
                DWORD extra = 0;
                if( ::ReadConsoleW( mHandle, wbuf + read, 1, &extra, NULL ) && extra == 1 ) {
                    ++read;
                }
            }

            wbuf[read] = L'\0'; // ensure zero terminated.
            // convert to UTF-8
            mBuffer = utf16toutf8( wbuf );
            // set the UTF-8 buffer as new content for read.
            setg( mBuffer.data(), mBuffer.data(), mBuffer.data() + mBuffer.size());

            if( gptr() >= egptr() ) {
                return traits_type::eof();
            }
        }

        return sgetc();
    }
};

template< typename T > requires std::derived_from< T, std::streambuf >
class Win32Console_InStream_Overwrite
{
public:
    Win32Console_InStream_Overwrite()
        : mOld( nullptr )
    {
        // only do if stdin is connected to a console/terminal.
        if( ::GetFileType( ::GetStdHandle( STD_INPUT_HANDLE ) ) == FILE_TYPE_CHAR ) {
            mStreamBuf = std::make_unique<T>();
            mOld = std::cin.rdbuf( mStreamBuf.get() );
            
        }
    }
    ~Win32Console_InStream_Overwrite()
    {
        if( mOld ) {
            std::cin.rdbuf( mOld );
        }
    }
private:
    std::unique_ptr<T>  mStreamBuf;
    std::streambuf     *mOld;
};


int wmain( int argc, wchar_t **argv )
{
    std::vector<std::string> args;
    try {
        args.reserve( argc );
        args.emplace_back( build_abs_exe_path( utf16toutf8( argv[0] ) ) );
        for( int i = 1; i < argc; ++i ) {
            args.emplace_back( utf16toutf8( argv[i] ) );
        }
    } catch( std::exception const &ex ) {
        std::cerr << ex.what() << std::endl;
        return EXIT_FAILURE;
    }

    Win32Console_InStream_Overwrite< Win32Console_InStream_Utf16ToUtf8Buffer >   make_std_cin_read_utf8;
    // NOTE: Unfortunately, this does not cover the echo from std::getline( std::cin,...) !!! :-(
    //       I opened a bug report for the broken echo, but it seems like MS is not gonna fix it ...
    //       https://developercommunity.visualstudio.com/t/Bug-in-WIN32-API-ReadConsoleW-for-UTF-16/10252965
    // UPDATE: It is fixed now in WindowsTerminal >= 1.19
    // The below is not needed anyway becasue we use libfmt
    //Win32Console_OutStream_Overwrite< Win32Console_OutStream_Utf8ToUtf16Buffer > make_std_cout_print_utf8;

    // check for correct console mode for be able to do colorized output (old conhost.exe needs this)
#if TEASCRIPT_USE_FMT_COLOR
    DWORD mode = 0;
    if( not ::GetConsoleMode( ::GetStdHandle( STD_OUTPUT_HANDLE ), &mode ) ) {
        std::cerr << "Warning: Console mode could not be detected. Colorized output might produce garbage on screen." << std::endl;
    } else if( not (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) ) {
        if( not ::SetConsoleMode( ::GetStdHandle( STD_OUTPUT_HANDLE ), mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT ) ) {
            std::cerr << "\nERROR! Colorized output could not be activated for your console!\n" 
                      << "Colorized output may produce garbage on the screen.\n" 
                      << "Please, use a modern Console Host (e.g., Windows Terminal) or\n" 
                      << "compile with TEASCRIPT_USE_FMT_COLOR set to 0.\n" << std::endl;
        }
    }
#endif

    return main_teascript_exe( args );
}
#else // On Linux/Unix-like we just build the args vector. strings are UTF-8 already.
int main( int argc, char **argv )
{
    std::vector<std::string> args;
    try {
        args.reserve( argc );
        args.emplace_back( build_abs_exe_path( argv[0] ) );
        for( int i = 1; i < argc; ++i ) {
            args.emplace_back( argv[i] );
        }
    } catch( std::exception const &ex ) {
        std::cerr << ex.what() << std::endl;
        return EXIT_FAILURE;
    }

    return main_teascript_exe( args );
}
#endif

