diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,643 +1,642 @@ cmake_minimum_required (VERSION 3.18) project(Lgi) if (WIN32) set(CMAKE_SYSTEM_VERSION 10.0.19041.0) endif() if (FALSE) # dump all vars for debugging. get_cmake_property(_variableNames VARIABLES) list (SORT _variableNames) foreach (_variableName ${_variableNames}) message(STATUS "${_variableName}=${${_variableName}}") endforeach() endif() include(build.cmake) set(CMAKE_OSX_ARCHITECTURES "x86_64") set(CMAKE_CXX_STANDARD 14) set(LGI_VERSION_MAJOR 1) set(LGI_VERSION_MINOR 0) if (UNIX AND NOT APPLE AND NOT HAIKU) set(LINUX TRUE) endif() if (WIN32) message("CMAKE_GENERATOR=${CMAKE_GENERATOR}") message("CMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}") if (CMAKE_GENERATOR MATCHES "Win64" OR CMAKE_GENERATOR_PLATFORM MATCHES "x64") set(WORDSIZE 64) set(OPENSSL_DIR "c:/Program Files/OpenSSL-Win64") else() set(WORDSIZE 32) set(OPENSSL_DIR "c:/Program Files (x86)/OpenSSL-Win32") endif() set(OPENSSL_INCLUDE_DIR "${OPENSSL_DIR}/include") if (CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION MATCHES "14393") message("SDK too old: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}") endif() endif() get_directory_property(hasParent PARENT_DIRECTORY) if(hasParent) set(LGI_DIR "${CMAKE_CURRENT_LIST_DIR}" PARENT_SCOPE) endif() set(LGI_DIR "${CMAKE_CURRENT_LIST_DIR}") # External libs: get_filename_component(CODE ../.. ABSOLUTE) get_filename_component(CODELIB ${CODE}/../codelib ABSOLUTE) if (NOT EXISTS "${CODELIB}") message(FATAL_ERROR "Expecting external libraries in: ${CODELIB}") endif() message("CODELIB=${CODELIB}") if(hasParent) set(CODELIB "${CODELIB}" PARENT_SCOPE) endif() set(ICONV_PATH "${CODELIB}/libiconv") if (NOT EXISTS "${ICONV_PATH}/include") if (APPLE) if (EXISTS "/opt/local/include/iconv.h") set(ICONV_PATH "/opt/local") else() find_package(Iconv REQUIRED) endif() elseif (LINUX OR HAIKU) find_package(Iconv REQUIRED) else() message(FATAL_ERROR "Missing iconv at: ${ICONV_PATH}") endif() endif() if (HAIKU) find_package(PNG REQUIRED) set(LIBPNG_TARGET PNG::PNG) else() get_filename_component(PNG_LIBRARY "${CODELIB}/libpng" ABSOLUTE) if (EXISTS ${PNG_LIBRARY} AND NOT TARGET libpng16) add_subdirectory(${PNG_LIBRARY} libpng) set(LIBPNG_TARGET libpng16) endif() endif() if (HAIKU) find_package(JPEG REQUIRED) else() get_filename_component(JPEG_LIBRARY "${CODELIB}/libjpeg-9a" ABSOLUTE) if (NOT TARGET libjpeg9a) add_subdirectory(${CODELIB}/libjpeg-9a libjpeg) endif() endif() if (APPLE) # SET(GUI_TYPE MACOSX_BUNDLE) find_library(COCOA_LIBRARY Cocoa) mark_as_advanced (COCOA_LIBRARY) set(PUBLIC_LIBS ${COCOA_LIBRARY}) elseif (LINUX) find_package(PkgConfig) pkg_check_modules(GTK3 REQUIRED gtk+-3.0) pkg_check_modules(GST REQUIRED gstreamer-1.0>=1.4) set(PUBLIC_LIBS pthread crypt z) endif() # Lgi source code: set (CommonSource src/common/Gdc2/15Bit.cpp src/common/Gdc2/16Bit.cpp src/common/Gdc2/24Bit.cpp src/common/Gdc2/32Bit.cpp src/common/Gdc2/64Bit.cpp src/common/Gdc2/8Bit.cpp src/common/Gdc2/Alpha.cpp include/lgi/common/Filter.h src/common/Gdc2/Filters/Filter.cpp include/lgi/common/DisplayString.h src/common/Gdc2/Font/DisplayString.cpp include/lgi/common/StringLayout.h src/common/Gdc2/Font/StringLayout.cpp include/lgi/common/Font.h src/common/Gdc2/Font/Font.cpp src/common/Gdc2/Font/FontSystem.cpp src/common/Gdc2/Font/FontType.cpp src/common/Gdc2/Font/TypeFace.cpp include/lgi/common/Charset.h src/common/Gdc2/Font/Charset.cpp include/lgi/common/Colour.h src/common/Gdc2/Colour.cpp include/lgi/common/Gdc2.h src/common/Gdc2/GdcCommon.cpp include/lgi/common/Rect.h src/common/Gdc2/Rect.cpp src/common/Gdc2/Surface.cpp include/lgi/common/Path.h src/common/Gdc2/Path/Path.cpp src/common/Gdc2/Tools/ColourReduce.cpp include/lgi/common/Containers.h src/common/General/Containers.cpp include/lgi/common/DateTime.h src/common/General/DateTime.cpp src/common/General/ExeCheck.cpp include/lgi/common/File.h src/common/General/FileCommon.cpp include/lgi/common/Growl.h src/common/General/Growl.cpp src/common/General/Password.cpp src/common/Hash/md5/md5.c src/common/Hash/sha1/sha1.c src/common/Net/Base64.cpp src/common/Net/Uri.cpp include/lgi/common/Net.h src/common/Net/Net.cpp - src/common/Net/NetTools.cpp src/common/Net/MDStringToDigest.cpp src/common/Lgi/Alert.cpp include/lgi/common/Css.h src/common/Lgi/Css.cpp include/lgi/common/CssTools.h src/common/Lgi/CssTools.cpp include/lgi/common/FindReplaceDlg.h src/common/Lgi/FindReplace.cpp include/lgi/common/FontSelect.h src/common/Lgi/FontSelect.cpp src/common/Lgi/GuiUtils.cpp src/common/Lgi/Input.cpp include/lgi/common/Library.h src/common/Lgi/Library.cpp include/lgi/common/Mru.h src/common/Lgi/Mru.cpp include/lgi/common/Mutex.h src/common/Lgi/Mutex.cpp src/common/Lgi/Object.cpp include/lgi/common/OptionsFile.h src/common/Lgi/OptionsFile.cpp include/lgi/common/SubProcess.h src/common/Lgi/SubProcess.cpp include/lgi/common/Stream.h src/common/Lgi/Stream.cpp src/common/Lgi/MemStream.cpp include/lgi/common/Thread.h src/common/Lgi/ThreadCommon.cpp src/common/Lgi/ThreadEvent.cpp include/lgi/common/ToolTip.h src/common/Lgi/ToolTip.cpp include/lgi/common/TrayIcon.h src/common/Lgi/TrayIcon.cpp include/lgi/common/Variant.h src/common/Lgi/Variant.cpp include/lgi/common/App.h src/common/Lgi/AppCommon.cpp include/lgi/common/View.h src/common/Lgi/ViewCommon.cpp include/lgi/common/Window.h src/common/Lgi/WindowCommon.cpp include/lgi/common/DragAndDrop.h src/common/Lgi/DragAndDropCommon.cpp include/lgi/common/Lgi.h src/common/Lgi/LgiCommon.cpp src/common/Lgi/LMsg.cpp src/common/Lgi/Rand.cpp include/lgi/common/DropFiles.h src/common/Lgi/DropFiles.cpp include/lgi/common/Menu.h src/common/Lgi/MenuCommon.cpp include/lgi/common/LgiRes.h src/common/Resource/LgiRes.cpp src/common/Resource/Res.cpp src/common/Skins/Gel/Gel.cpp include/lgi/common/DocView.h src/common/Text/DocView.cpp include/lgi/common/StringClass.h include/lgi/common/LgiString.h src/common/Text/String.cpp include/lgi/common/TextView3.h src/common/Text/TextView3.cpp include/lgi/common/TextView4.h src/common/Text/TextView4.cpp include/lgi/common/Token.h src/common/Text/Token.cpp include/lgi/common/Unicode.h src/common/Text/Unicode.cpp include/lgi/common/Unicode.h src/common/Text/Utf8.cpp include/lgi/common/XmlTree.h src/common/Text/XmlTree.cpp include/lgi/common/Button.h src/common/Widgets/Button.cpp include/lgi/common/Bitmap.h src/common/Widgets/Bitmap.cpp include/lgi/common/Box.h src/common/Widgets/Box.cpp src/common/Widgets/ItemContainer.cpp include/lgi/common/List.h src/common/Widgets/List.cpp include/lgi/common/Panel.h src/common/Widgets/Panel.cpp include/lgi/common/Popup.h src/common/Widgets/Popup.cpp include/lgi/common/ProgressDlg.h src/common/Widgets/ProgressDlg.cpp include/lgi/common/RadioGroup.h src/common/Widgets/RadioGroup.cpp include/lgi/common/Splitter.h src/common/Widgets/Splitter.cpp include/lgi/common/StatusBar.h src/common/Widgets/StatusBar.cpp include/lgi/common/TableLayout.h src/common/Widgets/TableLayout.cpp include/lgi/common/TabView.h src/common/Widgets/TabView.cpp include/lgi/common/TextLabel.h src/common/Widgets/TextLabel.cpp include/lgi/common/ToolBar.h src/common/Widgets/ToolBar.cpp include/lgi/common/Tree.h src/common/Widgets/Tree.cpp include/lgi/common/CheckBox.h include/lgi/common/Combo.h include/lgi/common/Edit.h include/lgi/common/Layout.h include/lgi/common/Slider.h include/lgi/common/Progress.h include/lgi/common/ClipBoard.h ) if (APPLE) set(Source ${CommonSource} src/common/Widgets/CheckBox.cpp src/common/Widgets/Combo.cpp src/common/Widgets/ScrollBar.cpp src/common/Widgets/Edit.cpp src/common/Lgi/Layout.cpp src/common/Widgets/Progress.cpp src/common/Widgets/Slider.cpp src/mac/cocoa/ShowFileProp_Mac.mm src/mac/common/MemDC.cpp src/mac/common/PrintDC.cpp src/mac/common/ScreenDC.cpp src/mac/cocoa/File.mm src/mac/cocoa/Mem.mm src/mac/cocoa/App.mm src/mac/cocoa/ClipBoard.mm src/mac/cocoa/DragAndDrop.mm src/mac/cocoa/General.mm src/mac/cocoa/FileSelect.mm src/mac/cocoa/Menu.mm src/mac/cocoa/Printer.mm src/mac/cocoa/Thread.mm src/mac/cocoa/View.mm src/mac/cocoa/Widgets.mm src/mac/cocoa/Window.mm src/mac/cocoa/CocoaView.mm src/mac/cocoa/Gdc2.mm ) elseif (LINUX) set(Source ${CommonSource} src/linux/Gtk/Gdc2.cpp src/linux/Gtk/MemDC.cpp src/linux/Gtk/PrintDC.cpp src/linux/Gtk/ScreenDC.cpp src/linux/Gtk/LgiWidget.cpp src/linux/General/File.cpp src/linux/General/Mem.cpp src/linux/General/ShowFileProp_Linux.cpp src/linux/Lgi/App.cpp src/linux/Lgi/ClipBoard.cpp src/linux/Lgi/DragAndDrop.cpp src/linux/Lgi/General.cpp src/linux/Lgi/Layout.cpp src/linux/Lgi/Menu.cpp src/linux/Lgi/Printer.cpp src/linux/Lgi/Thread.cpp src/linux/Lgi/View.cpp src/linux/Lgi/Widgets.cpp src/linux/Lgi/Window.cpp src/common/Lgi/FileSelect.cpp src/common/Widgets/CheckBox.cpp src/common/Widgets/Edit.cpp src/common/Widgets/ScrollBar.cpp src/common/Widgets/Combo.cpp src/common/Widgets/RadioGroup.cpp src/common/Widgets/Progress.cpp src/common/Widgets/Slider.cpp ) elseif (MSVC) set(Source ${CommonSource} src/win/General/Lgi.manifest src/win/Gdc2/Gdc2.cpp src/win/Gdc2/GdiLeak.cpp src/win/Gdc2/MemDC.cpp src/win/Gdc2/PrintDC.cpp src/win/Gdc2/ScreenDC.cpp src/win/General/File.cpp src/win/General/Mem.cpp src/win/General/ShowFileProp_Win.cpp src/win/INet/MibAccess.cpp src/win/Lgi/App.cpp src/win/Lgi/ClipBoard.cpp src/win/Lgi/DragAndDrop.cpp src/win/Lgi/FileSelect.cpp src/win/Lgi/General.cpp src/win/Lgi/Layout.cpp src/win/Lgi/Menu.cpp src/win/Lgi/Printer.cpp src/win/Lgi/ScrollBar.cpp src/win/Lgi/Thread.cpp src/win/Lgi/View.cpp src/win/Lgi/Window.cpp src/win/Lgi/LgiException.cpp src/win/Widgets/Button_Win.cpp src/common/Widgets/CheckBox.cpp # Use XP check box src/win/Widgets/Combo_Win.cpp src/win/Widgets/Dialog_Win.cpp src/win/Widgets/Edit_Win.cpp src/win/Widgets/RadioGroup_Win.cpp src/win/Widgets/Slider_Win.cpp src/win/Widgets/Progress_Win.cpp) elseif (HAIKU) set(Source ${CommonSource} src/common/Lgi/FileSelect.cpp src/common/Widgets/Combo.cpp src/common/Widgets/Edit.cpp src/common/Widgets/CheckBox.cpp src/common/Widgets/ScrollBar.cpp src/common/Widgets/Progress.cpp src/common/Widgets/Slider.cpp src/haiku/App.cpp src/haiku/ClipBoard.cpp src/haiku/DragAndDrop.cpp src/haiku/File.cpp src/haiku/Gdc2.cpp src/haiku/General.cpp src/haiku/Layout.cpp src/haiku/Mem.cpp src/haiku/MemDC.cpp src/haiku/Menu.cpp src/haiku/PrintDC.cpp src/haiku/Printer.cpp src/haiku/ScreenDC.cpp src/haiku/ShowFileProp_Haiku.cpp src/haiku/Thread.cpp src/haiku/View.cpp src/haiku/Widgets.cpp src/haiku/Window.cpp) endif () add_library(lgi SHARED ${Source}) add_library(lgi-stub STATIC src/common/Gdc2/Font/Charset.cpp src/common/General/Containers.cpp src/common/General/DateTime.cpp src/common/General/FileCommon.cpp src/common/Lgi/Library.cpp src/common/Lgi/LgiCommon.cpp src/common/Lgi/Mutex.cpp src/common/Lgi/Stream.cpp src/common/Lgi/ThreadCommon.cpp src/common/Lgi/Variant.cpp src/common/Lgi/LMsg.cpp src/common/Lgi/Rand.cpp src/common/Text/String.cpp src/common/Text/Token.cpp src/common/Text/Unicode.cpp) target_compile_definitions(lgi-stub PRIVATE LGI_LIBRARY LGI_STATIC) target_include_directories(lgi-stub PUBLIC include) if (DEFINED LGI_LIB_POSTFIX) set_target_properties(lgi PROPERTIES DEBUG_POSTFIX ${LGI_LIB_POSTFIX}d RELEASE_POSTFIX ${LGI_LIB_POSTFIX}) endif() if (APPLE) set_target_properties(lgi PROPERTIES XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_WEAK "YES" ) set_target_properties(lgi PROPERTIES FRAMEWORK true) set_target_properties(lgi PROPERTIES XCODE_ATTRIBUTE_INSTALL_PATH @executable_path/../Frameworks/) target_include_directories(lgi PUBLIC include/lgi/mac/cocoa /opt/local/include PRIVATE private/mac ) target_link_libraries(lgi PUBLIC ${EXTRA_LIBS}) target_compile_options(lgi PUBLIC -Wno-nullability-completeness) set(OBJCPP_SOURCE src/common/Gdc2/Font/FontSystem.cpp src/common/Gdc2/GdcCommon.cpp src/common/Lgi/LgiCommon.cpp src/common/Lgi/GuiUtils.cpp src/common/Lgi/Process.cpp src/common/Lgi/ViewCommon.cpp src/common/Lgi/TrayIcon.cpp src/common/Lgi/LMsg.cpp src/common/Lgi/ViewCommon.cpp src/common/Lgi/SubProcess.cpp src/common/Lgi/AppCommon.cpp src/common/Lgi/DropFiles.cpp src/common/Widgets/Popup.cpp src/common/General/DateTime.cpp src/mac/common/MemDC.cpp src/mac/common/ScreenDC.cpp src/mac/cocoa/General.cpp ) set_source_files_properties(${OBJCPP_SOURCE} PROPERTIES COMPILE_FLAGS "-x objective-c++") target_compile_definitions(lgi PUBLIC MAC LGI_COCOA ) elseif (LINUX) target_compile_definitions(lgi PUBLIC LINUX POSIX ) target_compile_options(lgi PUBLIC -fPIC -w -fno-inline -fpermissive ) target_include_directories(lgi PUBLIC include/lgi/linux/Gtk include/lgi/linux ${GTK3_INCLUDE_DIRS} ${GST_INCLUDE_DIRS} PRIVATE private/linux ) target_link_libraries(lgi PUBLIC ${GTK3_LIBRARIES} ${GST_LIBRARIES} magic ) elseif (MSVC) target_compile_definitions(lgi PUBLIC WINDOWS WIN64 _WINDLL _UNICODE UNICODE ) target_compile_options(lgi PRIVATE /W2 ) target_include_directories(lgi PUBLIC include/lgi/win PRIVATE private/win) target_link_libraries(lgi PUBLIC ComCtl32.lib Ws2_32.lib UxTheme.lib imm32.lib) target_sources(lgi-stub PRIVATE src/win/General/File.cpp src/win/General/Mem.cpp src/win/Lgi/General.cpp src/win/Lgi/Thread.cpp) target_include_directories(lgi-stub PUBLIC include/lgi/win) target_compile_definitions(lgi-stub PUBLIC WINDOWS _UNICODE UNICODE) elseif (HAIKU) target_include_directories(lgi-stub PUBLIC include/lgi/haiku PRIVATE private/haiku) target_compile_definitions(lgi-stub PUBLIC HAIKU _GNU_SOURCE) target_include_directories(lgi PUBLIC include/lgi/haiku PRIVATE private/haiku) target_compile_definitions(lgi PUBLIC HAIKU _GNU_SOURCE) target_link_libraries(lgi PUBLIC gnu network be) endif() target_link_libraries(lgi PUBLIC ${PUBLIC_LIBS}) target_compile_definitions(lgi PUBLIC LGI_RES PRIVATE LGI_LIBRARY ) target_include_directories(lgi PUBLIC include PRIVATE private/common ${PNGLIB_INCLUDE_DIR} ${ICONV_PATH}/include ) set_target_properties(lgi PROPERTIES LINKER_LANGUAGE CXX) add_subdirectory(src/common/Net/libntlm-0.4.2) if(LINUX) add_subdirectory(src/linux/CrashHandler) endif() # main apps add_subdirectory(Ide) add_subdirectory(Lvc) add_subdirectory(ResourceEditor) # other subfolders of smaller utils and tests add_subdirectory(utils) add_subdirectory(test) diff --git a/src/common/Coding/ScriptCompiler.cpp b/src/common/Coding/ScriptCompiler.cpp --- a/src/common/Coding/ScriptCompiler.cpp +++ b/src/common/Coding/ScriptCompiler.cpp @@ -1,3866 +1,3866 @@ /// \file #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/LexCpp.h" #include "lgi/common/LgiString.h" #include "ScriptingPriv.h" // #define DEBUG_SCRIPT_FILE "Mail Filters Menu.script" #define GetTok(c) ((c) < Tokens.Length() ? Tokens[c] : NULL) #define GetTokType(c) ((c) < Tokens.Length() ? ExpTok.Find(Tokens[c]) : TNone) #define GV_VARIANT GV_MAX const char *sDebugger = "Debugger"; int LFunctionInfo::_Infos = 0; enum LTokenType { #define _(type, str) T##type, #include "TokenType.h" #undef _ }; struct LinkFixup { int Tok; size_t Offset; int Args; LFunctionInfo *Func; }; struct Node { typedef LArray NodeExp; struct VariablePart { LVariant Name; NodeExp Array; bool Call; LArray Args; VariablePart() { Call = false; } ~VariablePart() { Args.DeleteObjects(); } }; // Hierarchy NodeExp Child; // One of the following are valid: LOperator Op = OpNull; // -or- bool Constant = false; int Tok = -1; LArray Lst; LTokenType ConstTok = TNone; // -or- LFunc *ContextFunc = NULL; LArray Args; // -or- LFunctionInfo *ScriptFunc = NULL; // -or- LArray Variable; // Used during building LVarRef Reg; LVarRef ArrayIdx; void Init() { Reg.Empty(); ArrayIdx.Empty(); } void SetOp(LOperator o, int t) { Init(); Op = o; Tok = t; } void SetConst(int t, LTokenType e) { Init(); Constant = true; Tok = t; ConstTok = e; } void SetConst(LArray &list_tokens, LTokenType e) { Init(); Constant = true; Lst = list_tokens; ConstTok = e; } void SetContextFunction(LFunc *m, int tok) { Init(); ContextFunc = m; Tok = tok; } void SetScriptFunction(LFunctionInfo *m, int tok) { Init(); ScriptFunc = m; Tok = tok; } void SetVar(char16 *Var, int t) { Init(); Variable[0].Name = Var; Tok = t; } bool IsVar() { return Variable.Length() > 0; } bool IsContextFunc() { return ContextFunc != 0; } bool IsScriptFunc() { return ScriptFunc != 0; } bool IsConst() { return Constant; } }; LCompiledCode::LCompiledCode() : Globals(SCOPE_GLOBAL), Debug(0, -1) { SysContext = NULL; UserContext = NULL; } LCompiledCode::LCompiledCode(LCompiledCode ©) : Globals(SCOPE_GLOBAL), Debug(0, -1) { *this = copy; } LCompiledCode::~LCompiledCode() { for (auto e: Externs) e->InUse = false; Externs.DeleteObjects(); } LCompiledCode &LCompiledCode::operator =(const LCompiledCode &c) { Globals = c.Globals; ByteCode = c.ByteCode; Types = c.Types; Debug = c.Debug; Methods = c.Methods; FileName = c.FileName; Source = c.Source; return *this; } LFunctionInfo *LCompiledCode::GetMethod(const char *Name, bool Create) { for (auto &m: Methods) { const char *Fn = m->GetName(); if (!strcmp(Fn, Name)) { return m; } } if (Create) { LAutoRefPtr n(new LFunctionInfo(Name)); if (n) { Methods.Add(n); return n; } } return 0; } int LCompiledCode::ObjectToSourceAddress(size_t ObjAddr) { auto Idx = Debug.Find(ObjAddr); return Idx < 0 ? 1 : Idx; } const char *LCompiledCode::AddrToSourceRef(size_t ObjAddr) { static char Status[256]; size_t Addr = ObjAddr; int LineNum = ObjectToSourceAddress(Addr); // Search for the start of the instruction... while (Addr > 0 && LineNum < 0) LineNum = ObjectToSourceAddress(--Addr); char *Dir = FileName ? strrchr(FileName, DIR_CHAR) : NULL; size_t PathLen = Dir ? Dir - FileName : 0; LString FileRef = FileName ? (PathLen > 24 ? Dir + 1 : FileName.Get()) : "(untitled)"; if (LineNum >= 0) sprintf_s( Status, sizeof(Status), "%s:%i", FileRef.Get(), LineNum); else sprintf_s(Status, sizeof(Status), "%s:0x%x", FileRef.Get(), (int)ObjAddr); return Status; } LVariant *LCompiledCode::Set(const char *Name, LVariant &v) { int i = Globals.Var(Name, true); if (i >= 0) { Globals[i] = v; return &Globals[i]; } return 0; } template void UnEscape(T *t) { T *i = t, *o = t; while (*i) { if (*i == '\\') { i++; switch (*i) { case '\\': *o++ = '\\'; i++; break; case 'n': *o++ = '\n'; i++; break; case 'r': *o++ = '\r'; i++; break; case 't': *o++ = '\t'; i++; break; case 'b': *o++ = '\b'; i++; break; } } else *o++ = *i++; } *o++ = 0; } class TokenRanges { LArray FileNames; struct Range { int Start, End; ssize_t File; int Line; }; LArray Ranges; char fl[MAX_PATH_LEN + 32]; public: TokenRanges() { Empty(); } void Empty() { Ranges.Length(0); } ssize_t Length() { return Ranges.Length(); } /// Gets the file/line at a given token const char *operator [](ssize_t Tok) { Range *r = NULL; for (unsigned i=0; i= rng->Start && Tok <= rng->End) { r = rng; break; } } if (!r) r = &Ranges.Last(); if (r->File >= (ssize_t)FileNames.Length()) { LAssert(!"Invalid file index."); return "#err: invalid file index"; } else { sprintf_s(fl, sizeof(fl), "%s:%i", FileNames[r->File].Get(), r->Line); } return fl; } const char *GetLine(int Line) { if (Ranges.Length() > 0) { Range &r = Ranges.Last(); if (r.File < (ssize_t)FileNames.Length()) sprintf_s(fl, sizeof(fl), "%s:%i", FileNames[r.File].Get(), Line); else return "#err: invalid file index."; } else { sprintf_s(fl, sizeof(fl), "$unknown:%i", Line); } return fl; } ssize_t GetFileIndex(const char *FileName) { for (unsigned i=0; iFile != FileId || r->Line != Line) { // Start a new range... r = &Ranges.New(); r->Start = r->End = TokIndex; r->File = FileId; r->Line = Line; } else { r->End = TokIndex; } } }; /// Scripting language compiler implementation class LCompilerPriv : public LCompileTools, public LScriptUtils { LHashTbl, LVariantType> Types; size_t JumpLoc; LArray> FuncMem; public: LScriptContext *SysCtx; LScriptContext *UserCtx; LCompiledCode *Code; LStream *Log; LArray Tokens; TokenRanges Lines; char16 *Script; LHashTbl, LFunc*> Methods; uint64_t Regs; LArray Scopes; LArray Fixups; LHashTbl, char16*> Defines; LHashTbl, LTokenType> ExpTok; LDom *ScriptArgs; LVarRef ScriptArgsRef; bool ErrShowFirstOnly; LArray ErrLog; bool Debug; #ifdef _DEBUG LString::Array RegAllocators; #endif LCompilerPriv() { Debug = false; ErrShowFirstOnly = true; SysCtx = NULL; UserCtx = NULL; Code = 0; Log = 0; Script = 0; Regs = 0; ScriptArgs = NULL; ScriptArgsRef.Empty(); #define LNULL NULL #define _(type, str) if (str) ExpTok.Add(L##str, T##type); #include "TokenType.h" #undef _ #undef LNULL Types.Add("int", GV_INT32); Types.Add("short", GV_INT32); Types.Add("long", GV_INT32); Types.Add("int8", GV_INT32); Types.Add("int16", GV_INT32); Types.Add("int32", GV_INT32); Types.Add("int64", GV_INT64); Types.Add("uint8", GV_INT32); Types.Add("uint16", GV_INT32); Types.Add("uint32", GV_INT32); Types.Add("uint64", GV_INT64); Types.Add("bool", GV_BOOL); Types.Add("boolean", GV_BOOL); Types.Add("double", GV_DOUBLE); Types.Add("float", GV_DOUBLE); Types.Add("char", GV_STRING); Types.Add("LDom", GV_DOM); Types.Add("void", GV_VOID_PTR); Types.Add("LDateTime", GV_DATETIME); Types.Add("GHashTable", GV_HASHTABLE); Types.Add("LOperator", GV_OPERATOR); Types.Add("LView", GV_GVIEW); Types.Add("LMouse", GV_LMOUSE); Types.Add("LKey", GV_LKEY); Types.Add("LVariant", GV_VARIANT); // Types.Add("binary", GV_BINARY); // Types.Add("List", GV_LIST); // Types.Add("LDom&", GV_DOMREF); } ~LCompilerPriv() { Empty(); } void Empty() { SysCtx = NULL; UserCtx = NULL; DeleteObj(Code); Log = 0; Tokens.DeleteArrays(); Lines.Empty(); DeleteArray(Script); Defines.DeleteArrays(); Methods.Empty(); } /// Prints the error message bool OnError ( /// The line number (less than 0) or Token index (greater than 0) int LineOrTok, /// The format for the string (printf) const char *Msg, /// Variable argument list ... ) { if (!ErrShowFirstOnly || ErrLog.Length() == 0) { char Buf[512]; va_list Arg; va_start(Arg, Msg); #ifndef WIN32 #define _vsnprintf vsnprintf #endif vsprintf_s(Buf, sizeof(Buf), Msg, Arg); Log->Print("%s - Error: %s\n", LineOrTok < 0 ? Lines.GetLine(-LineOrTok) : Lines[LineOrTok], Buf); va_end(Arg); ErrLog.New() = Buf; } return false; // Always return false to previous caller } void DebugInfo(int Tok) { const char *Line = Lines[Tok]; if (Line) { const char *c = strrchr(Line, ':'); if (c) { Code->Debug.Add(Code->ByteCode.Length(), ::atoi(c+1)); } } } /// Assemble a zero argument instruction bool Asm0(int Tok, uint8_t Op) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1)) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; } else return false; return true; } /// Assemble one arg instruction bool Asm1(int Tok, uint8_t Op, LVarRef a) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 5)) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; *p.r++ = a; } else return false; return true; } /// Assemble two arg instruction bool Asm2(int Tok, uint8_t Op, LVarRef a, LVarRef b) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 9)) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; *p.r++ = a; *p.r++ = b; } else return false; return true; } /// Assemble three arg instruction bool Asm3(int Tok, uint8_t Op, LVarRef a, LVarRef b, LVarRef c) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1 + (sizeof(LVarRef) * 3) )) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; *p.r++ = a; *p.r++ = b; *p.r++ = c; } else return false; return true; } /// Assemble four arg instruction bool Asm4(int Tok, uint8_t Op, LVarRef a, LVarRef b, LVarRef c, LVarRef d) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1 + (sizeof(LVarRef) * 4) )) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; if (!a.Valid()) AllocNull(a); *p.r++ = a; if (!b.Valid()) AllocNull(b); *p.r++ = b; if (!c.Valid()) AllocNull(c); *p.r++ = c; if (!d.Valid()) AllocNull(d); *p.r++ = d; } else return false; return true; } /// Assemble 'n' length arg instruction bool AsmN(int Tok, uint8_t Op, LArray &Args) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1 + (sizeof(LVarRef) * Args.Length()) )) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; for (unsigned i=0; iDefines.Find(wName); if (v) { // Is it a number? Cause we should really convert to an int if possible... #define INVALID_CONVERSION -11111 int64 i = INVALID_CONVERSION; if (!Strnicmp(v, L"0x", 2)) i = Atoi(v, 16, INVALID_CONVERSION); else if (IsDigit(*v) || strchr("-.", *v)) i = Atoi(v); if (i != INVALID_CONVERSION) Value = i; else Value = v; return true; } return false; } }; /// This will look at resolving the expression to something simpler before storing it... void SimplifyDefine(LScriptEngine &Eng, char16 *Name, LAutoWString &Value) { LString Exp = Value.Get(); if (Exp.Find("(") >= 0) // Filter out simple expressions... { LVariant Result; CompilerDefineSource Src(this); if (Eng.EvaluateExpression(&Result, &Src, Exp)) { auto Val = Result.CastString(); if (Val) { // LgiTrace("Simplify: %S -> %s\n", Value.Get(), Val); Value.Reset(Utf8ToWide(Val)); } } } } /// Convert the source from one big string into an array of tokens bool Lex(char *Source, const char *FileName) { char16 *w = Utf8ToWide(Source); if (!w) return OnError(0, "Couldn't convert source to wide chars."); LScriptEngine Eng(NULL, NULL, NULL); ssize_t FileIndex = Lines.GetFileIndex(FileName); int Line = 1; char16 *s = w, *t; while ((t = LexCpp(s, LexStrdup, NULL, &Line))) { if (*t == '#') { ssize_t Len; if (!StrnicmpW(t + 1, sInclude, Len = StrlenW(sInclude))) { LAutoWString Raw(LexCpp(s, LexStrdup)); LAutoWString File(TrimStrW(Raw, (char16*)L"\"\'")); if (File) { LVariant v; LString IncCode; v.OwnStr(File.Release()); if (UserCtx) { IncCode = UserCtx->GetIncludeFile(v.Str()); if (IncCode) { Lex(IncCode, v.Str()); } else { DeleteArray(t); return OnError(-Line, "Ctx failed to include '%s'", v.Str()); } } else { if (LIsRelativePath(v.Str())) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), FileName, ".."); LMakePath(p, sizeof(p), p, v.Str()); v = p; } if (LFileExists(v.Str())) { LFile f(v.Str()); if (f && (IncCode = f.Read())) { Lex(IncCode, v.Str()); } else { DeleteArray(t); return OnError(-Line, "Couldn't read '%s'", v.Str()); } } else { DeleteArray(t); return OnError(-Line, "Couldn't include '%s'", v.Str()); } } } else { OnError(-Line, "No file for #include."); } } else if (!StrnicmpW(t + 1, sDefine, Len = StrlenW(sDefine))) { LAutoWString Name(LexCpp(s, LexStrdup)); if (Name && IsAlpha(*Name)) { char16 *Start = s; while (*Start && strchr(WhiteSpace, *Start)) Start++; char16 *Eol = StrchrW(Start, '\n'); if (!Eol) Eol = Start + StrlenW(Start); while (Eol > Start && strchr(WhiteSpace, Eol[-1])) Eol--; LAutoWString Value(NewStrW(Start, Eol - Start)); SimplifyDefine(Eng, Name, Value); Defines.Add(Name, Value.Release()); s = Eol; } } DeleteArray(t); continue; } if (!ResolveDefine(t, FileIndex, Line)) { Lines.Add((int)Tokens.Length(), FileIndex, Line); Tokens.Add(t); } } // end of "while (t = LexCpp)" loop if (!Script) { Script = w; } else { DeleteArray(w); } return true; } /// Create a null var ref void AllocNull(LVarRef &r) { r.Scope = SCOPE_GLOBAL; if (Code->Globals.NullIndex < 0) Code->Globals.NullIndex = (int)Code->Globals.Length(); r.Index = Code->Globals.NullIndex; Code->Globals[r.Index].Type = GV_NULL; } /// Allocate a variant and ref LVariant *PreAllocVariant(LVarRef &r) { r.Scope = SCOPE_GLOBAL; r.Index = (int)Code->Globals.Length(); return &Code->Globals[r.Index]; } /// Allocate a constant double void AllocConst(LVarRef &r, double d) { r.Scope = SCOPE_GLOBAL; if (Code->Globals.Length()) { // Check for existing int LVariant *p = &Code->Globals[0]; LVariant *e = p + Code->Globals.Length(); while (p < e) { if (p->Type == GV_DOUBLE && p->Value.Dbl == d) { r.Index = (int) (p - &Code->Globals[0]); return; } p++; } } r.Index = (int)Code->Globals.Length(); Code->Globals[r.Index] = d; } /// Allocate a constant bool void AllocConst(LVarRef &r, bool b) { r.Scope = SCOPE_GLOBAL; if (Code->Globals.Length()) { // Check for existing int LVariant *p = &Code->Globals[0]; LVariant *e = p + Code->Globals.Length(); while (p < e) { if (p->Type == GV_BOOL && p->Value.Bool == b) { r.Index = (int)(p - &Code->Globals[0]); return; } p++; } } r.Index = (int)Code->Globals.Length(); Code->Globals[r.Index] = b; } /// Allocate a constant int void AllocConst(LVarRef &r, int64 i) { r.Scope = SCOPE_GLOBAL; LVariantType Type = i <= INT_MAX && i >= INT_MIN ? GV_INT32 : GV_INT64; if (Code->Globals.Length()) { // Check for existing int LVariant *p = &Code->Globals[0]; LVariant *e = p + Code->Globals.Length(); while (p < e) { if (p->Type == Type) { if (Type == GV_INT32 && p->Value.Int == i) { r.Index = (int) (p - &Code->Globals[0]); return; } else if (Type == GV_INT64 && p->Value.Int64 == i) { r.Index = (int) (p - &Code->Globals[0]); return; } } p++; } } // Allocate new global r.Index = (int)Code->Globals.Length(); if (Type == GV_INT32) Code->Globals[r.Index] = (int32)i; else Code->Globals[r.Index] = i; } void AllocConst(LVarRef &r, int i) { AllocConst(r, (int64)i); } /// Allocate a constant string void AllocConst(LVarRef &r, char *s, ssize_t len = -1) { LAssert(s != 0); if (len < 0) len = strlen(s); r.Scope = SCOPE_GLOBAL; r.Index = (int)Code->Globals.Length(); for (unsigned i=0; iGlobals.Length(); i++) { if (Code->Globals[i].Type == GV_STRING) { char *c = Code->Globals[i].Str(); if (*s == *c && strcmp(s, c) == 0) { r.Index = i; return; } } } LVariant &v = Code->Globals[r.Index]; v.Type = GV_STRING; if ((v.Value.String = NewStr(s, len))) { UnEscape(v.Value.String); } } /// Allocate a constant wide string void AllocConst(LVarRef &r, char16 *s, ssize_t len) { LAssert(s != 0); char *utf = WideToUtf8(s, len); if (!utf) utf = NewStr(""); r.Scope = SCOPE_GLOBAL; r.Index = (int)Code->Globals.Length(); for (unsigned i=0; iGlobals.Length(); i++) { if (Code->Globals[i].Type == GV_STRING) { char *c = Code->Globals[i].Str(); if (*utf == *c && strcmp(utf, c) == 0) { r.Index = i; DeleteArray(utf); return; } } } LVariant &v = Code->Globals[r.Index]; v.Type = GV_STRING; if ((v.Value.String = utf)) { UnEscape(v.Value.String); } } /// Find a variable by name, creating it if needed LVarRef FindVariable(LVariant &Name, bool Create) { LVarRef r = {0, -1}; // Look for existing variable... ssize_t i; for (i=Scopes.Length()-1; i>=0; i--) { r.Index = Scopes[i]->Var(Name.Str(), false); if (r.Index >= 0) { r.Scope = Scopes[i]->Scope; return r; } } // Create new variable on most recent scope i = Scopes.Length() - 1; r.Index = Scopes[i]->Var(Name.Str(), Create); if (r.Index >= 0) { r.Scope = Scopes[i]->Scope; } return r; } /// Build asm to assign a var ref bool AssignVarRef(Node &n, LVarRef &Value) { /* Examples and what their assembly should look like: a = Value Assign a <- Value a[b] = Value R0 = AsmExpression(b); ArraySet a[R0] <- Value a[b].c = Value R0 = AsmExpression(b); R0 = ArrayGet a[R0] DomSet(R0, "c", Null, Value) a[b].c[d] R0 = AsmExpression(b); R0 = ArrayGet a[R0]; R1 = AsmExpression(d); R0 = DomGet(R0, "c", R1); ArraySet R0[R1] = Value a.b[c].d = Value R1 = AsmExpression(c); R0 = DomGet(a, "b", R1); DomSet(R1, "d", Null, Value) // resolve initial array if (parts > 1 && part[0].array) { // cur = ArrayGet(part[0].var, part[0].array) } else { cur = part[0] } // dom loop over loop (p = 0 to parts - 2) { if (part[p+1].array) arr = exp(part[p+1].array) else arr = null cur = DomGet(cur, part[p+1].var, arr) } // final part if (part[parts-1].arr) { arr = exp(part[parts-1].arr) ArraySet(cur, arr) } else { DomSet(cur, part[parts-1].var, null, value) } */ if (!Value.Valid()) { LAssert(!"Invalid value to assign.\n"); return false; } if (!n.IsVar()) { LAssert(!"Target must be a variable."); return false; } // Gets the first part of the variable. LVarRef Cur = FindVariable(n.Variable[0].Name, true); if (!Cur.Valid()) return false; if (n.Variable.Length() > 1) { // Do any initial array dereference if (n.Variable[0].Array.Length()) { // Assemble the array index's expression into 'Idx' LVarRef Idx; Idx.Empty(); if (!AsmExpression(&Idx, n.Variable[0].Array)) return OnError(n.Tok, "Error creating bytecode for array index."); LVarRef Dst = Cur; if (!Dst.IsReg()) { AllocReg(Dst, n.Tok, _FL); } // Assemble array load instruction Asm3(n.Tok, IArrayGet, Dst, Cur, Idx); Cur = Dst; // Cleanup DeallocReg(Idx); } // Do all the DOM "get" instructions unsigned p; for (p=0; pOwnStr(NewStrW(t + 1, Len - 2)); } else if (StrchrW(t, '.')) { // double *v = atof(t); } else if (t[0] == '0' && tolower(t[1]) == 'x') { // hex integer *v = htoi(t + 2); } else { // decimal integer int64 i = Atoi(t); if (i <= INT_MAX && i >= INT_MIN) *v = (int32)i; else *v = i; } return true; } /// Convert a token stream to a var ref bool TokenToVarRef(Node &n, LVarRef *&LValue) { if (n.Reg.Valid()) { if (LValue && n.Reg != *LValue) { // Need to assign to LValue LAssert(*LValue != n.Reg); Asm2(n.Tok, IAssign, *LValue, n.Reg); } } else { if (n.IsVar()) { // Variable unsigned p = 0; bool ArrayDeindexed = false; bool HasScriptArgs = Scopes.Length() <= 1 && ScriptArgs != NULL; LVarRef v = FindVariable(n.Variable[p].Name, /*!HasScriptArgs ||*/ LValue != NULL); if (v.Index < 0) { if (HasScriptArgs) { if (AllocReg(v, n.Tok, _FL)) { char16 *VarName = GetTok((unsigned)n.Tok); if (!ScriptArgsRef.Valid()) { // Setup the global variable to address the script argument variable ScriptArgsRef.Scope = SCOPE_GLOBAL; ScriptArgsRef.Index = (int)Code->Globals.Length(); LVariant &v = Code->Globals[ScriptArgsRef.Index]; v.Type = GV_DOM; v.Value.Dom = ScriptArgs; } LVarRef Name, Array; AllocConst(Name, VarName, -1); if (n.Variable[p].Array.Length()) { if (!AsmExpression(&Array, n.Variable[p].Array)) return OnError(n.Tok, "Can't assemble array expression."); ArrayDeindexed = true; } else AllocNull(Array); Asm4(n.Tok, IDomGet, v, ScriptArgsRef, Name, Array); } else return false; } else { Node::VariablePart &vp = n.Variable[p]; return OnError(n.Tok, "Undefined variable: %s", vp.Name.Str()); } } else { if (v.Scope == SCOPE_OBJECT) { // We have to load the variable into a register LVarRef Reg, ThisPtr, MemberIndex, Null; if (!AllocReg(Reg, n.Tok, _FL)) return OnError(n.Tok, "Couldn't alloc register."); ThisPtr.Scope = SCOPE_LOCAL; ThisPtr.Index = 0; AllocConst(MemberIndex, v.Index); AllocNull(Null); Asm4(n.Tok, IDomGet, Reg, ThisPtr, MemberIndex, Null); v = Reg; // Object variable now in 'Reg' } if (n.ConstTok == TTypeId) { if (!v.IsReg()) { // Because we are casting to it's DOM ptr, // make sure it's a register first so we don't lose the // actual variable. LVarRef reg; if (!AllocReg(reg, n.Tok, _FL)) return OnError(n.Tok, "Couldn't alloc register."); LAssert(reg != v); Asm2(n.Tok, IAssign, reg, v); v = reg; } // Casting to DOM will give as access to the type info for a LCustomType. // This currently doesn't work with other types :( Asm1(n.Tok, ICast, v); Code->ByteCode.Add(GV_DOM); } } n.Reg = v; LValue = NULL; LAssert(v.Scope != SCOPE_OBJECT); // Does it have an array deref? if (!ArrayDeindexed && n.Variable[p].Array.Length()) { // Evaluate the array indexing expression if (!AsmExpression(&n.ArrayIdx, n.Variable[p].Array)) { return OnError(n.Tok, "Error creating byte code for array index."); } // Do we need to create code to load the value from the array? LVarRef Val; if (AllocReg(Val, n.Tok, _FL)) { Asm3(n.Tok, IArrayGet, Val, n.Reg, n.ArrayIdx); n.Reg = Val; } else return OnError(n.Tok, "Error allocating register."); } // Load DOM parts... for (++p; p Args; Node::VariablePart &Part = n.Variable[p]; Name.Empty(); Arr.Empty(); char *nm = Part.Name.Str(); AllocConst(Name, nm, strlen(nm)); if (Part.Array.Length()) { if (!AsmExpression(&Arr, Part.Array)) { return OnError(n.Tok, "Can't assemble array expression."); } } else if (Part.Call) { for (unsigned i=0; i Call; Call[0] = Dst; Call[1] = n.Reg; Call[2] = Name; // This must always be a global, the decompiler requires access // to the constant to know how many arguments to print. And it // can only see the globals. AllocConst(Call[3], (int)Args.Length()); Call.Add(Args); AsmN(n.Tok, IDomCall, Call); } else { Asm4(n.Tok, IDomGet, Dst, n.Reg, Name, Arr); } n.Reg = Dst; } } else if (n.IsConst()) { // Constant switch (n.ConstTok) { case TTrue: { AllocConst(n.Reg, true); break; } case TFalse: { AllocConst(n.Reg, false); break; } case TNull: { AllocNull(n.Reg); break; } default: { if (n.Lst.Length()) { // List/array constant LVariant *v = PreAllocVariant(n.Reg); if (v) { v->SetList(); for (unsigned i=0; i a(new LVariant); if (!ConvertStringToVariant(a, t)) break; v->Value.Lst->Insert(a.Release()); } } } else { char16 *t = Tokens[n.Tok]; if (*t == '\"' || *t == '\'') { // string ssize_t Len = StrlenW(t); AllocConst(n.Reg, t + 1, Len - 2); } else if (StrchrW(t, '.')) { // double AllocConst(n.Reg, atof(t)); } else if (t[0] == '0' && tolower(t[1]) == 'x') { // hex integer AllocConst(n.Reg, htoi(t + 2)); } else { // decimal integer AllocConst(n.Reg, Atoi(t)); } } break; } } LValue = NULL; } else if (n.IsContextFunc()) { // Method call, create byte codes to put func value into n.Reg LVarRef *OutRef; if (LValue) { OutRef = LValue; } else { if (!AllocReg(n.Reg, n.Tok, _FL)) { return OnError(n.Tok, "Can't allocate register for method return value."); } OutRef = &n.Reg; } DebugInfo(n.Tok); LArray a; for (unsigned i=0; iByteCode.Length(); ssize_t Size = 1 + sizeof(LFunc*) + sizeof(LVarRef) + 2 + (a.Length() * sizeof(LVarRef)); Code->ByteCode.Length(Len + Size); LScriptPtr p; uint8_t *Start = &Code->ByteCode[Len]; p.u8 = Start; *p.u8++ = ICallMethod; *p.fn++ = n.ContextFunc; n.ContextFunc->InUse = true; *p.r++ = *OutRef; *p.u16++ = (uint16) n.Args.Length(); for (unsigned i=0; iGetName(); if (*FnName == 'D' && !_stricmp(FnName, sDebugger)) { Asm0(n.Tok, IDebug); return true; } if (n.ScriptFunc->GetParams().Length() != n.Args.Length()) { if (n.ScriptFunc->ValidStartAddr()) { // Function is already defined and doesn't have the right arg count... return OnError( n.Tok, "Wrong number of parameters: %i (%s expects %i)", n.Args.Length(), FnName, n.ScriptFunc->GetParams().Length()); } else { // Maybe it's defined later or in a separate file? // Or maybe it's just not defined at all? // Can't know until later... flag it as a unresolved external... // // Allow the assembly for the call to occur. } } // Call to a script function, create byte code to call function LVarRef *OutRef; if (LValue) { OutRef = LValue; } else { if (!AllocReg(n.Reg, n.Tok, _FL)) { return OnError(n.Tok, "Can't allocate register for method return value."); } OutRef = &n.Reg; } DebugInfo(n.Tok); LArray a; for (unsigned i=0; iByteCode.Length(); size_t Size = 1 + // instruction sizeof(uint32_t) + // address of function sizeof(uint16) + // size of frame sizeof(LVarRef) + // return value 2 + // number of args (a.Length() * sizeof(LVarRef)); // args Code->ByteCode.Length(Len + Size); LScriptPtr p; uint8_t *Start = &Code->ByteCode[Len]; p.u8 = Start; *p.u8++ = ICallScript; if (n.ScriptFunc->ValidStartAddr() && n.ScriptFunc->ValidFrameSize()) { // Compile func address straight into code... *p.u32++ = n.ScriptFunc->GetStartAddr(); *p.u16++ = n.ScriptFunc->GetFrameSize(); } else { // Add link time fix up LinkFixup &Fix = Fixups.New(); Fix.Tok = n.Tok; Fix.Args = (int)n.Args.Length(); Fix.Offset = p.u8 - &Code->ByteCode[0]; Fix.Func = n.ScriptFunc; *p.u32++ = 0; *p.u16++ = 0; } *p.r++ = *OutRef; *p.u16++ = (uint16) n.Args.Length(); for (unsigned i=0; i e(new Node::NodeExp); if (!e) return OnError(Cur, "Mem alloc error."); if (!Expression(Cur, *e)) return OnError(Cur, "Couldn't parse func call argument expression."); vp.Args.Add(e.Release()); t = GetTok(Cur); if (!t) return OnError(Cur, "Unexpected end of file."); if (!StricmpW(t, sComma)) Cur++; else if (!StricmpW(t, sEndRdBracket)) break; else return OnError(Cur, "Expecting ',', didn't get it."); } } t = GetTok(Cur+1); } // Check for DOM operator... if (StricmpW(t, sPeriod) == 0) { // Got Dom operator Cur += 2; t = GetTok(Cur); if (!t) return OnError(Cur, "Unexpected eof."); Var.Variable.New().Name = t; } else break; } return true; } /// Parse expression into a node tree bool Expression(uint32_t &Cur, LArray &n, int Depth = 0) { if (Cur >= Tokens.Length()) return OnError(Cur, "Unexpected end of file."); char16 *t; bool PrevIsOp = true; while ((t = Tokens[Cur])) { LTokenType Tok = ExpTok.Find(t); if (Tok == TTypeId) { char16 *v; if (!DoTypeId(Cur, v)) return false; Node &Var = n.New(); t = v; Cur--; DoVariableNode(Cur, Var, t); Var.ConstTok = TTypeId; Cur++; } else if (Tok == TStartRdBracket) { Cur++; auto &Next = n.New(); if (!Expression(Cur, Next.Child, Depth + 1)) return false; PrevIsOp = false; if (Next.Child.Length() > 0) Next.Tok = Next.Child[0].Tok; } else if (Tok == TEndRdBracket) { if (Depth > 0) Cur++; break; } else if (Tok == TComma || Tok == TSemiColon) { break; } else if (Depth == 0 && Tok == TEndSqBracket) { break; } else if (Tok == TTrue || Tok == TFalse || Tok == TNull) { n.New().SetConst(Cur++, Tok); } else { LOperator o = IsOp(t, PrevIsOp); if (o != OpNull) { // Operator PrevIsOp = 1; n.New().SetOp(o, Cur); } else { PrevIsOp = 0; LVariant m; m = t; LFunc *f = Methods.Find(m.Str()); LFunctionInfo *sf = 0; char16 *Next = GetTok(Cur+1); bool IsFunctionCall = !StricmpW(Next, sStartRdBracket); if (f && IsFunctionCall) { Node &Call = n.New(); Call.SetContextFunction(f, Cur++); // Now parse arguments // Get the start bracket if ((t = GetTok(Cur))) { if (StricmpW(t, sStartRdBracket) == 0) Cur++; else return OnError(Cur, "Function missing '('"); } else return OnError(Cur, "No token."); // Parse the args as expressions while ((t = GetTok(Cur))) { LTokenType Tok = ExpTok.Find(t); if (Tok == TComma) { // Do nothing... Cur++; } else if (Tok == TEndRdBracket) { break; } else if (Tok == TSemiColon) { return OnError(Cur, "Unexpected ';'"); } else if (!Expression(Cur, Call.Args.New())) { return OnError(Cur, "Can't parse function argument."); } } } else if ( (sf = Code->GetMethod(m.Str(), IsFunctionCall)) ) { Node &Call = n.New(); Call.SetScriptFunction(sf, Cur++); // Now parse arguments // Get the start bracket if ((t = GetTok(Cur))) { if (StricmpW(t, sStartRdBracket) == 0) Cur++; else return OnError(Cur, "Function missing '('"); } else return OnError(Cur, "No token."); // Parse the args as expressions while ((t = GetTok(Cur))) { if (StricmpW(t, sComma) == 0) { // Do nothing... Cur++; } else if (StricmpW(t, sEndRdBracket) == 0) { break; } else if (!Expression(Cur, Call.Args[Call.Args.Length()])) { return OnError(Cur, "Can't parse function argument."); } } } else if (IsAlpha(*t)) { // Variable... Node &Var = n.New(); if (!DoVariableNode(Cur, Var, t)) return false; } else if (*t == '\'' || *t == '\"' || LIsNumber(t)) { // Constant string or number n.New().SetConst(Cur, TLiteral); } else if (Tok == TStartCurlyBracket) { // List definition LArray Values; Cur++; int Index = 0; while ((t = Tokens[Cur])) { LTokenType Tok = ExpTok.Find(t); if (Tok == TComma) { Index++; } else if (Tok == TEndCurlyBracket) { break; } else if (*t == '\'' || *t == '\"' || LIsNumber(t)) { Values[Index] = Cur; } else { return OnError(Cur, "Unexpected token '%S' in list definition", t); } Cur++; } n.New().SetConst(Values, TLiteral); } else { // Unknown return OnError(Cur, "Unknown token '%S'", t); } } Cur++; } } return true; } /// Allocate a register (must be mirrored with DeallocReg) bool AllocReg(LVarRef &r, int LineOrTok, const char *file, int line) { for (int i=0; iPrint("CompileError:Register[%i] allocated by %s\n", n, a); } } #endif return false; } /// Deallocate a register bool DeallocReg(LVarRef &r) { if (r.Scope == SCOPE_REGISTER && r.Index >= 0) { int Bit = 1 << r.Index; // LAssert((Bit & Regs) != 0); Regs &= ~Bit; #ifdef _DEBUG RegAllocators[r.Index].Empty(); #endif } return true; } /// Count allocated registers int RegAllocCount() { int c = 0; for (int i=0; i &n) { LStringPipe e; for (unsigned i=0; iMethod.Get()); } else if (n[i].ScriptFunc) { e.Print("%s(...)", n[i].ScriptFunc->GetName()); } else { e.Print("#err#"); } } return e.NewStr(); } /// Creates byte code to evaluate an expression bool AsmExpression ( /// Where the result got stored LVarRef *Result, /// The nodes to create code for LArray &n, /// The depth of recursion int Depth = 0 ) { // Resolve any sub-expressions and store their values for (unsigned i = 0; i < n.Length(); i++) { auto &k = n[i]; if (!k.IsVar() && k.Child.Length()) { AllocReg(k.Reg, k.Tok, _FL); AsmExpression(&k.Reg, k.Child, Depth + 1); } } while (n.Length() > 1) { // Find which operator to handle first #ifdef _DEBUG size_t StartLength = n.Length(); #endif int OpIdx = -1; int Prec = -1; int Ops = 0; for (unsigned i=0; i Jmp; if (!TokenToVarRef(a, NullRef)) return OnError(a.Tok, "Can't convert left token to var ref."); if (Op == OpAnd) { // Jump over 'b' if 'a' is FALSE Jmp.Reset(new LJumpZero(this, a.Tok, a.Reg, false)); } else if (Op == OpOr) { // Jump over 'b' if 'a' is TRUE } if (!TokenToVarRef(b, LValue)) return OnError(b.Tok, "Can't convert right token to var ref."); LVarRef Reg; Reg.Empty(); if (a.Reg.Scope != SCOPE_REGISTER) { if (AllocReg(Reg, a.Tok, _FL)) { LAssert(Reg != a.Reg); Asm2(a.Tok, IAssign, Reg, a.Reg); a.Reg = Reg; } else return OnError(a.Tok, "Can't alloc register, Regs=0x%x", Regs); } Asm2(a.Tok, Op, a.Reg, b.Reg); if (Op == OpPlusEquals || Op == OpMinusEquals || Op == OpMulEquals || Op == OpDivEquals) { AssignVarRef(a, a.Reg); } } if (a.Reg != b.Reg) DeallocReg(b.Reg); n.DeleteAt(OpIdx+1, true); n.DeleteAt(OpIdx, true); // Result is in n[OpIdx-1] (ie the 'a' node) } else { LAssert(!"Not a valid type"); return OnError(n[0].Tok, "Not a valid type."); } #ifdef _DEBUG if (StartLength == n.Length()) { // No nodes removed... infinite loop! LAssert(!"No nodes removed."); return false; } #endif } if (n.Length() == 1) { auto &k = n[0]; if (!k.Reg.Valid()) { LVarRef *NullRef = NULL; if (!TokenToVarRef(k, NullRef)) { return false; } } if (Result) { if (Result->Valid() && *Result != k.Reg) DeallocReg(*Result); *Result = k.Reg; } else { DeallocReg(k.Reg); } return true; } return false; } /// Parses and assembles an expression bool DoExpression(uint32_t &Cur, LVarRef *Result) { LArray n; if (Expression(Cur, n)) { bool Status = AsmExpression(Result, n); return Status; } return false; } /// Converts a variable to it's type information bool DoTypeId(uint32_t &Cur, char16 *&Var) { if (GetTokType(Cur) != TTypeId) return OnError(Cur, "Expecting 'typeid'."); Cur++; if (GetTokType(Cur) != TStartRdBracket) return OnError(Cur, "Expecting '('."); Cur++; char16 *t = GetTok(Cur); if (!t || !IsAlpha(*t)) return OnError(Cur, "Expecting variable name."); Cur++; if (GetTokType(Cur) != TEndRdBracket) return OnError(Cur, "Expecting ')'."); Cur++; Var = t; return true; } /// Parses statements bool DoStatements(uint32_t &Cur, bool *LastWasReturn, bool MoreThanOne = true) { while (Cur < Tokens.Length()) { char16 *t = GetTok(Cur); if (!t) break; LTokenType Tok = ExpTok.Find(t); switch (Tok) { case TTypeId: { return OnError(Cur, "typeif only valid in an expression."); } case TSemiColon: { Cur++; break; } case TDebug: { Asm0(Cur++, IDebug); break; } case TBreakPoint: { Asm0(Cur++, IBreakPoint); break; } case TReturn: { Cur++; if (!DoReturn(Cur)) return false; if (LastWasReturn) *LastWasReturn = true; break; } case TEndCurlyBracket: case TFunction: { return true; } case TIf: { if (!DoIf(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } case TFor: { if (!DoFor(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } case TWhile: { if (!DoWhile(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } case TExtern: { if (!DoExtern(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } default: { if (!DoExpression(Cur, 0)) return false; LTokenType Tok = ExpTok.Find(GetTok(Cur)); if (Tok == TSemiColon) Cur++; if (LastWasReturn) *LastWasReturn = false; break; } } if (!MoreThanOne) break; } return true; } /// Parses if/else if/else construct bool DoIf(uint32_t &Cur) { Cur++; char16 *t; if (GetTokType(Cur) == TStartRdBracket) { Cur++; // Compile and asm code to evaluate the expression LVarRef Result; int ExpressionTok = Cur; Result.Empty(); if (DoExpression(Cur, &Result)) { t = GetTok(Cur); if (!t || StricmpW(t, sEndRdBracket)) return OnError(Cur, "if missing ')'."); Cur++; t = GetTok(Cur); if (!t) return OnError(Cur, "if missing body statement."); // Output the jump instruction LAutoPtr Jmp(new LJumpZero(this, ExpressionTok, Result)); if (!StricmpW(t, sStartCurlyBracket)) { // Statement block Cur++; while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } else { // Single statement if (!DoStatements(Cur, NULL, false)) return false; } // Check for else... if ((t = GetTok(Cur)) && StricmpW(t, sElse) == 0) { // Add a jump for the "true" part of the expression to // jump over the "else" part. Asm0(Cur, IJump); size_t JOffset = Code->ByteCode.Length(); if (Code->ByteCode.Length(JOffset + 4)) { // Initialize the ptr to zero int32 *Ptr = (int32*)&Code->ByteCode[JOffset]; *Ptr = 0; // Resolve jz to here... Jmp.Reset(); // Compile the else block Cur++; if ((t = GetTok(Cur)) && StricmpW(t, sStartCurlyBracket) == 0) { // 'Else' Statement block Cur++; while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } else { // Single statement if (!DoStatements(Cur, NULL, false)) return false; } // Resolve the "JOffset" jump that takes execution of // the 'true' part over the 'else' part Ptr = (int32*)&Code->ByteCode[JOffset]; *Ptr = (int32) (Code->ByteCode.Length() - JOffset - sizeof(int32)); if (*Ptr == 0) { // Empty statement... so delete the Jump instruction Code->ByteCode.Length(JOffset - 1); } } else OnError(Cur, "Mem alloc"); } return true; } } else return OnError(Cur, "if missing '('"); return false; } LArray &GetByteCode() { return Code->ByteCode; } class LJumpZero { LCompilerPriv *Comp; int JzOffset; public: LJumpZero(LCompilerPriv *d, int Tok, LVarRef &r, bool DeallocReg = true) { // Create jump instruction to jump over the body if the expression evaluates to false Comp = d; Comp->Asm1(Tok, IJumpZero, r); if (DeallocReg) Comp->DeallocReg(r); JzOffset = (int) Comp->GetByteCode().Length(); Comp->GetByteCode().Length(JzOffset + sizeof(int32)); } ~LJumpZero() { // Resolve jump int32 *Ptr = (int32*) &Comp->GetByteCode()[JzOffset]; *Ptr = (int32) (Comp->GetByteCode().Length() - (JzOffset + sizeof(int32))); } }; /// Parses while construct bool DoWhile(uint32_t &Cur) { Cur++; char16 *t = GetTok(Cur); if (!t || StricmpW(t, sStartRdBracket)) return OnError(Cur, "Expecting '(' after 'while'"); Cur++; // Store start of condition code size_t ConditionStart = Code->ByteCode.Length(); // Compile condition evalulation LVarRef r; r.Empty(); if (!DoExpression(Cur, &r)) return false; // Create jump instruction to jump over the body if the expression evaluates to false { LJumpZero Jump(this, Cur, r); if (!(t = GetTok(Cur)) || StricmpW(t, sEndRdBracket)) return OnError(Cur, "Expecting ')'"); Cur++; // Compile the body of the loop if (!(t = GetTok(Cur))) return OnError(Cur, "Unexpected eof."); Cur++; if (StricmpW(t, sStartCurlyBracket) == 0) { // Block while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } else { // Single statement DoStatements(Cur, NULL, false); } // Jump to condition evaluation at 'ConditionStart' Asm0(Cur, IJump); size_t JOffset = Code->ByteCode.Length(); Code->ByteCode.Length(JOffset + sizeof(int32)); int32 *Ptr = (int32*) &Code->ByteCode[JOffset]; *Ptr = (int32) (ConditionStart - Code->ByteCode.Length()); } return true; } /// Parses for construct bool DoFor(uint32_t &Cur) { /* For loop asm structure: +---------------------------+ | Pre-condition expression | +---------------------------+ | Eval loop contdition exp. |<--+ | | | | JUMP ZERO (jumpz) |---+-+ +---------------------------+ | | | Body of loop... | | | | | | | | | | | | | | | | | | | | | | | +---------------------------+ | | | Post-cond. expression | | | | | | | | JUMP |---+ | +---------------------------+ | | Following code... |<----+ . . */ Cur++; char16 *t = GetTok(Cur); if (!t || StricmpW(t, sStartRdBracket)) return OnError(Cur, "Expecting '(' after 'for'"); Cur++; // Compile initial statement LVarRef r; r.Empty(); t = GetTok(Cur); if (!t) return false; if (StricmpW(t, sSemiColon) && !DoExpression(Cur, 0)) return false; t = GetTok(Cur); // Look for ';' if (!t || StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';'"); Cur++; // Store start of condition code size_t ConditionStart = Code->ByteCode.Length(); // Compile condition evalulation if (!DoExpression(Cur, &r)) return false; { LJumpZero Jmp(this, Cur, r); t = GetTok(Cur); // Look for ';' if (!t || StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';'"); Cur++; // Compile the post expression code size_t PostCodeStart = Code->ByteCode.Length(); t = GetTok(Cur); if (StricmpW(t, sEndRdBracket) && !DoExpression(Cur, 0)) return false; // Store post expression code in temp variable LArray PostCode; size_t PostCodeLen = Code->ByteCode.Length() - PostCodeStart; if (PostCodeLen) { PostCode.Length(PostCodeLen); memcpy(&PostCode[0], &Code->ByteCode[PostCodeStart], PostCodeLen); // Delete the post expression off the byte code, we are putting it after the block code Code->ByteCode.Length(PostCodeStart); } // Look for ')' t = GetTok(Cur); if (!t || StricmpW(t, sEndRdBracket)) return OnError(Cur, "Expecting ')'"); Cur++; // Compile body of loop if ((t = GetTok(Cur)) && StricmpW(t, sStartCurlyBracket) == 0) { Cur++; while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } // Add post expression code if (PostCodeLen) { size_t Len = Code->ByteCode.Length(); Code->ByteCode.Length(Len + PostCodeLen); memcpy(&Code->ByteCode[Len], &PostCode[0], PostCodeLen); } // Jump to condition evaluation at 'ConditionStart' Asm0(Cur, IJump); size_t JOffset = Code->ByteCode.Length(); Code->ByteCode.Length(JOffset + sizeof(int32)); int32 *Ptr = (int32*) &Code->ByteCode[JOffset]; *Ptr = (int32) (ConditionStart - Code->ByteCode.Length()); } return true; } /// Compiles return construct bool DoReturn(uint32_t &Cur) { LVarRef ReturnValue; char16 *t; int StartTok = Cur; ReturnValue.Empty(); LArray Exp; if (!Expression(Cur, Exp)) { return OnError(Cur, "Failed to compile return expression."); } else if (!AsmExpression(&ReturnValue, Exp)) { return OnError(Cur, "Failed to assemble return expression."); } if (!(t = GetTok(Cur)) || StricmpW(t, sSemiColon)) { return OnError(Cur, "Expecting ';' after return expression."); } Asm1(StartTok, IRet, ReturnValue); DeallocReg(ReturnValue); Cur++; return true; } // Compile a method definition bool DoFunction ( /// Cursor token index uint32_t &Cur, /// [Optional] Current struct / class. /// If not NULL this is a method definition for said class. LCustomType *Struct = NULL ) { bool Status = false; bool LastInstIsReturn = false; if (!JumpLoc) { size_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 5)) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = IJump; *p.i32++ = 0; JumpLoc = Len + 1; } else OnError(Cur, "Mem alloc failed."); } LString FunctionName; LCustomType::Method *StructMethod = NULL; // Member function of script struct/class LFunctionInfo *ScriptMethod = NULL; // Standalone scripting function char16 *Name = GetTok(Cur); if (Name) { Cur++; char16 *t = GetTok(Cur); if (!t || StricmpW(t, sStartRdBracket)) return OnError(Cur, "Expecting '(' in function."); FunctionName = Name; // Parse parameters LArray Params; Cur++; while ((t = GetTok(Cur))) { if (IsAlpha(*t)) { Params.New() = t; Cur++; if (!(t = GetTok(Cur))) goto UnexpectedFuncEof; } if (!StricmpW(t, sComma)) ; else if (!StricmpW(t, sEndRdBracket)) { Cur++; break; } else goto UnexpectedFuncEof; Cur++; } if (Struct) { StructMethod = Struct->DefineMethod(FunctionName, Params, Code->ByteCode.Length()); } else { ScriptMethod = Code->GetMethod(FunctionName, true); if (!ScriptMethod) return OnError(Cur, "Can't define method '%s'.", FunctionName.Get()); ScriptMethod->GetParams() = Params; ScriptMethod->SetStartAddr((int32_t)Code->ByteCode.Length()); } // Parse start of body if (!(t = GetTok(Cur))) goto UnexpectedFuncEof; if (StricmpW(t, sStartCurlyBracket)) return OnError(Cur, "Expecting '{'."); // Setup new scope(s) LAutoPtr ObjectScope; if (Struct) { // The object scope has to be first so that local variables take // precedence over object member variables. if (ObjectScope.Reset(new LVariables(Struct))) Scopes.Add(ObjectScope); } LVariables LocalScope(SCOPE_LOCAL); if (Struct) LocalScope.Var("This", true); for (unsigned i=0; iFrameSize = (uint16)LocalScope.Length(); else if (ScriptMethod) ScriptMethod->SetFrameSize(LocalScope.Length()); else LAssert(!"What are you defining exactly?"); Status = true; Cur++; // LgiTrace("Added method %s @ %i, stack=%i, args=%i\n", f->Name.Str(), f->StartAddr, f->FrameSize, f->Params.Length()); break; } // Parse statement in the body if (!DoStatements(Cur, &LastInstIsReturn)) return OnError(Cur, "Can't compile function body."); } // Remove any scopes we created Scopes.PopLast(); if (Struct) Scopes.PopLast(); } if (!LastInstIsReturn) { LVarRef RetVal; AllocNull(RetVal); Asm1(Cur, IRet, RetVal); } return Status; UnexpectedFuncEof: return OnError(Cur, "Unexpected EOF in function."); } LExternFunc::ExternType ParseExternType(LArray &Strs) { LExternFunc::ExternType Type; Type.Out = false; Type.Ptr = 0; Type.ArrayLen = 1; Type.Base = GV_NULL; Type.Unsigned = false; bool InArray = false; for (unsigned i=0; i Tok; const char16 *t; while ((t = GetTok(Cur))) { if (!StricmpW(t, sStartRdBracket)) { Cur++; break; } else Tok.Add(t); Cur++; } if (Tok.Length() < 3) { return OnError(Cur, "Not enough tokens in extern decl."); } // First token is library name LExternFunc *e = new LExternFunc; if (!e) return OnError(Cur, "Alloc error."); Code->Externs.Add(e); e->Type = ExternFunc; char16 sQuotes[] = {'\'','\"',0}; LAutoWString LibName(TrimStrW(Tok[0], sQuotes)); e->Lib.Reset(WideToUtf8(LibName)); Tok.DeleteAt(0, true); e->Method = Tok.Last(); Tok.DeleteAt(Tok.Length()-1, true); if (!ValidStr(e->Method)) { Code->Externs.Length(Code->Externs.Length()-1); return OnError(Cur, "Failed to get extern method name."); } // Parse return type e->ReturnType = ParseExternType(Tok); // Parse argument types Tok.Length(0); while ((t = GetTok(Cur))) { if (!StricmpW(t, sEndRdBracket)) { e->ArgType.New() = ParseExternType(Tok); Cur++; break; } else if (!StricmpW(t, sComma)) { e->ArgType.New() = ParseExternType(Tok); } else Tok.Add(t); Cur++; } if ((t = GetTok(Cur))) { if (StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';' in extern decl."); Cur++; } Methods.Add(e->Method, e); return true; } struct ExpPart { LOperator Op; int Value; ExpPart() { Op = OpNull; Value = 0; } }; int Evaluate(LArray &Exp, size_t Start, size_t End) { LArray p; // Find outer brackets ssize_t e; for (e = End; e >= (ssize_t)Start; e--) { if (Exp[e][0] == ')') break; } for (size_t i = Start; i <= End; i++) { char16 *t = Exp[i]; if (*t == '(') { p.New().Value = Evaluate(Exp, i + 1, e - 1); i = e; } else { LOperator op = IsOp(t, 0); if (op) { p.New().Op = op; } else if (IsDigit(*t)) { p.New().Value = AtoiW(t); } else { LAssert(0); break; } } } while (p.Length() > 1) { int HighPrec = 32; int Idx = -1; for (unsigned i=0; i 0 && Idx < (int)p.Length() - 1) { switch (p[Idx].Op) { case OpPlus: p[Idx-1].Value += p[Idx+1].Value; break; case OpMinus: p[Idx-1].Value -= p[Idx+1].Value; break; case OpMul: p[Idx-1].Value *= p[Idx+1].Value; break; case OpDiv: if (p[Idx+1].Value) p[Idx-1].Value /= p[Idx+1].Value; else LAssert(!"Div 0"); break; default: LAssert(!"Impl me."); break; } p.DeleteAt(Idx, true); p.DeleteAt(Idx, true); } else { LAssert(0); break; } } else { LAssert(!"Impl me."); } } if (p.Length() == 1) { LAssert(p[0].Op == OpNull); return p[0].Value; } LAssert(0); return 0; } int ByteSizeFromType(char *s, LVariantType t) { switch (t) { case GV_INT32: { char n[16], *o = n; for (char *c = s; *c; c++) { if (IsDigit(*c) && o < n + sizeof(n) - 1) *o++ = *c; } *o++ = 0; int bits = ::atoi(n); switch (bits) { case 8: return 1; case 16: return 2; } return 4; break; } case GV_INT64: { return 8; } case GV_WSTRING: { return sizeof(char16); } default: break; } return 1; } /// Compiles struct construct bool DoStruct(uint32_t &Cur) { bool Status = false; // Parse struct name and setup a type char16 *t; LCustomType *Def = Code->Types.Find(t = GetTok(Cur)); if (!Def) Code->Types.Add(t, Def = new LCustomType(t)); Cur++; t = GetTok(Cur); if (!t || StricmpW(t, sStartCurlyBracket)) return OnError(Cur, "Expecting '{'"); Cur++; // Parse members while ((t = GetTok(Cur))) { // End of type def? LTokenType tt = ExpTok.Find(t); if (tt == TEndCurlyBracket) { Cur++; tt = GetTokType(Cur); if (!tt || tt != TSemiColon) return OnError(Cur, "Expecting ';' after '}'"); Status = true; break; } if (tt == TFunction) { Cur++; if (!DoFunction(Cur, Def)) return false; } else { // Parse member field LVariant TypeName = t; LCustomType *NestedType = 0; LVariantType Type = Types.Find(TypeName.Str()); if (!Type) { // Check other custom types NestedType = Code->GetType(t); if (!NestedType) return OnError(Cur, "Unknown type '%S' in struct definition.", t); // Ok, nested type. Type = GV_CUSTOM; } Cur++; if (!(t = GetTok(Cur))) goto EofError; bool Pointer = false; if (t[0] == '*' && t[1] == 0) { Pointer = true; Cur++; if (!(t = GetTok(Cur))) goto EofError; } LVariant Name = t; Cur++; if (!(t = GetTok(Cur))) goto EofError; int Array = 1; if (!StricmpW(t, sStartSqBracket)) { // Array Cur++; LArray Exp; while ((t = GetTok(Cur))) { Cur++; if (!StricmpW(t, sEndSqBracket)) break; Exp.Add(t); } Array = Evaluate(Exp, 0, Exp.Length()-1); } int MemberAddr = Def->AddressOf(Name.Str()); if (MemberAddr >= 0) return OnError(Cur, "Member '%s' can't be defined twice.", Name.Str()); if (NestedType) { if (!Def->DefineField(Name.Str(), NestedType, Array)) return OnError(Cur, "Failed to define field '%s'.", Name.Str()); } else { int Bytes = ByteSizeFromType(TypeName.Str(), Type); if (!Def->DefineField(Name.Str(), Type, Bytes, Array)) return OnError(Cur, "Failed to define field '%s'.", Name.Str()); } t = GetTok(Cur); if (StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';'"); Cur++; } } return Status; EofError: return OnError(Cur, "Unexpected EOF."); } /// Compiler entry point bool Compile() { uint32_t Cur = 0; JumpLoc = 0; // Setup the global scope Scopes.Length(0); Scopes.Add(&Code->Globals); // Compile the code... while (Cur < Tokens.Length()) { char16 *t = GetTok(Cur); if (!t) break; if (*t == '#' || StricmpW(t, sSemiColon) == 0) { Cur++; } else if (!StricmpW(t, sFunction)) { if (!DoFunction(++Cur)) return false; } else if (!StricmpW(t, sStruct)) { if (!DoStruct(++Cur)) return false; } else if (!StricmpW(t, sEndCurlyBracket)) { return OnError(Cur, "Not expecting '}'."); } else if (!StricmpW(t, sExtern)) { if (!DoExtern(++Cur)) return false; } else { if (JumpLoc) { LScriptPtr p; p.u8 = &Code->ByteCode[JumpLoc]; *p.u32 = (uint32_t) (Code->ByteCode.Length() - (JumpLoc + 4)); JumpLoc = 0; } if (!DoStatements(Cur, NULL)) { return OnError(Cur, "Statement compilation failed."); } } } if (JumpLoc) { LScriptPtr p; p.u8 = &Code->ByteCode[JumpLoc]; *p.u32 = (uint32_t) (Code->ByteCode.Length() - (JumpLoc + 4)); JumpLoc = 0; } // Do link time fix ups... for (unsigned i=0; iValidStartAddr()) { if (f.Args != f.Func->GetParams().Length()) { return OnError(f.Tok, "Function call '%s' has wrong arg count (caller=%i, method=%i).", f.Func->GetName(), f.Args, f.Func->GetParams().Length()); } else if (!f.Func->ValidFrameSize()) { return OnError(f.Tok, "Function call '%s' has no frame size.", f.Func->GetName()); } else { LScriptPtr p; p.u8 = &Code->ByteCode[f.Offset]; LAssert(*p.u32 == 0); *p.u32++ = f.Func->GetStartAddr(); *p.u16++ = f.Func->GetFrameSize(); } } else { return OnError(f.Tok, "Function '%s' not defined.", f.Func->GetName()); } } Fixups.Length(0); return true; } }; LCompiler::LCompiler() { d = new LCompilerPriv; } LCompiler::~LCompiler() { DeleteObj(d); } bool LCompiler::Compile ( LAutoPtr &Code, LScriptContext *SysContext, LScriptContext *UserContext, const char *FileName, const char *Script, LDom *Args ) { if (!Script) return false; LStringPipe p; #ifdef DEBUG_SCRIPT_FILE d->Debug = Stristr(FileName, DEBUG_SCRIPT_FILE) != NULL; #endif if (SysContext && SysContext->GetLog()) d->Log = SysContext->GetLog(); else if (UserContext && UserContext->GetLog()) d->Log = UserContext->GetLog(); else d->Log = &p; d->Methods.Empty(); if (SysContext) { LHostFunc *f = SysContext->GetCommands(); for (int i=0; f[i].Method; i++) { f[i].Context = SysContext; d->Methods.Add(f[i].Method, &f[i]); } } d->SysCtx = SysContext; d->UserCtx = UserContext; if (d->UserCtx) { LHostFunc *f = d->UserCtx->GetCommands(); for (int i=0; f[i].Method; i++) { f[i].Context = d->UserCtx; if (!d->Methods.Find(f[i].Method)) d->Methods.Add(f[i].Method, f+i); else { LgiTrace("%s:%i - Conflicting name of method in application's context: '%s'\n", _FL, f[i].Method.Get()); LAssert(!"Conflicting name of method in application's context."); } } } if (!Code) Code.Reset(new LCompiledCode); bool Status = false; d->Code = dynamic_cast(Code.Get()); if (d->Code) { d->Code->UserContext = UserContext; d->Code->SysContext = SysContext; d->Code->SetSource(FileName, Script); bool LexResult = d->Lex((char*)Script, FileName); if (LexResult) { d->ScriptArgs = Args; Status = d->Compile(); } else { d->OnError(0, "Failed to lex script.\n"); } } else { d->OnError(0, "Allocation failed.\n"); } d->Code = NULL; return Status; } ////////////////////////////////////////////////////////////////////// class LScriptEnginePrivate { public: LViewI *Parent; SystemFunctions SysContext; LScriptContext *UserContext; LCompiledCode *Code; LVmCallback *Callback; LVariant ReturnValue; LScriptEnginePrivate() { UserContext = NULL; Parent = NULL; Code = NULL; Callback = NULL; } }; LScriptEngine::LScriptEngine(LViewI *parent, LScriptContext *UserContext, LVmCallback *Callback) { d = new LScriptEnginePrivate; d->Parent = parent; d->UserContext = UserContext; d->Callback = Callback; d->SysContext.SetEngine(this); } LScriptEngine::~LScriptEngine() { DeleteObj(d); } LCompiledCode *LScriptEngine::GetCurrentCode() { return d->Code; } bool LScriptEngine::Compile(LAutoPtr &Obj, LScriptContext *UserContext, const char *Script, const char *FileName, LDom *Args) { if (!Script) { LAssert(!"Param error"); return NULL; } LCompiler Comp; return Comp.Compile(Obj, &d->SysContext, UserContext ? UserContext : d->UserContext, FileName, Script, Args); } LExecutionStatus LScriptEngine::Run(LCompiledCode *Obj, LVariant *Ret, const char *TempPath) { LExecutionStatus Status = ScriptError; d->Code = Obj; if (d->Code) { LVirtualMachine Vm(d->Callback); if (TempPath) Vm.SetTempPath(TempPath); Status = Vm.Execute(d->Code, 0, NULL, true, Ret ? Ret : &d->ReturnValue); d->Code = NULL; } return Status; } LExecutionStatus LScriptEngine::RunTemporary(LCompiledCode *Obj, char *Script, LVariant *Ret) { LExecutionStatus Status = ScriptError; LCompiledCode *Code = dynamic_cast(Obj); if (Script && Code) { LAutoPtr Temp(new LCompiledCode(*Code)); uint32_t TempLen = (uint32_t) Temp->Length(); d->Code = Temp; LCompiler Comp; if (Comp.Compile(Temp, &d->SysContext, d->UserContext, Temp->GetFileName(), Script, NULL)) { LVirtualMachine Vm(d->Callback); Status = Vm.Execute(dynamic_cast(Temp.Get()), TempLen, NULL, true, Ret ? Ret : &d->ReturnValue); } d->Code = NULL; } return Status; } bool LScriptEngine::EvaluateExpression(LVariant *Result, LDom *VariableSource, const char *Expression) { if (!Result || !VariableSource || !Expression) { LAssert(!"Param error"); return false; } // Create trivial script to evaluate the expression LString a; a.Printf("return %s;", Expression); - + // Compile the script LCompiler Comp; LAutoPtr Obj; - if (!Comp.Compile(Obj, NULL, NULL, NULL, a, VariableSource)) + if (!Comp.Compile(Obj, &d->SysContext, d->UserContext, NULL, a, VariableSource)) { LAssert(0); return false; } // Execute the script LVirtualMachine Vm(d->Callback); LCompiledCode *Code = dynamic_cast(Obj.Get()); auto ReturnVal = Result ? Result : &d->ReturnValue; LExecutionStatus s = Vm.Execute(Code, 0, NULL, true, ReturnVal); if (s != ScriptSuccess) { return false; } if (ReturnVal->Type == GV_INT64 && !(ReturnVal->Value.Int64 >> 32)) { *ReturnVal = ReturnVal->CastInt32(); } return true; } LStream *LScriptEngine::GetConsole() { if (d->SysContext.GetLog()) return d->SysContext.GetLog(); if (d->UserContext && d->UserContext->GetLog()) return d->UserContext->GetLog(); return NULL; } bool LScriptEngine::SetConsole(LStream *t) { d->SysContext.SetLog(t); if (d->UserContext) d->UserContext->SetLog(t); return true; } bool LScriptEngine::CallMethod(LCompiledCode *Obj, const char *Method, LScriptArguments &Args) { LCompiledCode *Code = dynamic_cast(Obj); if (!Code || !Method) return false; LFunctionInfo *i = Code->GetMethod(Method); if (!i) return false; LVirtualMachine Vm(d->Callback); Args.Vm = &Vm; LExecutionStatus Status = Vm.ExecuteFunction(Code, i, Args, NULL); Args.Vm = NULL; return Status != ScriptError; } LScriptContext *LScriptEngine::GetSystemContext() { return &d->SysContext; } diff --git a/src/common/Coding/ScriptLibrary.cpp b/src/common/Coding/ScriptLibrary.cpp --- a/src/common/Coding/ScriptLibrary.cpp +++ b/src/common/Coding/ScriptLibrary.cpp @@ -1,1238 +1,1250 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/SubProcess.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "ScriptingPriv.h" ////////////////////////////////////////////////////////////////////////////////////// char16 sChar[] = L"char"; char16 sInt[] = { 'i','n','t', 0 }; char16 sUInt[] = { 'u','i','n','t', 0 }; char16 sInt32[] = { 'i','n','t','3','2', 0 }; char16 sUInt32[] = { 'u','i','n','t','3','2', 0 }; char16 sInt64[] = { 'i','n','t','6','4', 0 }; char16 sHWND[] = { 'H','W','N','D', 0 }; char16 sDWORD[] = { 'D','W','O','R','D', 0 }; char16 sLPTSTR[] = { 's','L','P','T','S','T','R', 0 }; char16 sLPCTSTR[] = { 's','L','P','C','T','S','T','R', 0 }; char16 sElse[] = { 'e','l','s','e', 0 }; char16 sIf[] = { 'i','f',0 }; char16 sFunction[] = { 'f','u','n','c','t','i','o','n',0 }; char16 sExtern[] = { 'e','x','t','e','r','n',0 }; char16 sFor[] = { 'f','o','r',0 }; char16 sWhile[] = { 'w','h','i','l','e',0 }; char16 sReturn[] = { 'r','e','t','u','r','n',0 }; char16 sInclude[] = { 'i','n','c','l','u','d','e',0 }; char16 sDefine[] = { 'd','e','f','i','n','e',0 }; char16 sStruct[] = { 's','t','r','u','c','t',0 }; char16 sTrue[] = { 't','r','u','e',0 }; char16 sFalse[] = { 'f','a','l','s','e',0 }; char16 sNull[] = { 'n','u','l','l',0 }; char16 sOutParam[] = { '_','o','u','t','_',0}; char16 sHash[] = { '#', 0 }; char16 sPeriod[] = { '.', 0 }; char16 sComma[] = { ',', 0 }; char16 sSemiColon[] = { ';', 0 }; char16 sStartRdBracket[] = { '(', 0 }; char16 sEndRdBracket[] = { ')', 0 }; char16 sStartSqBracket[] = { '[', 0 }; char16 sEndSqBracket[] = { ']', 0 }; char16 sStartCurlyBracket[] = { '{', 0 }; char16 sEndCurlyBracket[] = { '}', 0 }; ////////////////////////////////////////////////////////////////////////////////////// LExecutionStatus LHostFunc::Call(LScriptContext *Ctx, LScriptArguments &Args) { return (Ctx->*(Func))(Args) ? ScriptSuccess : ScriptError; } const char *InstToString(GInstruction i) { #undef _i #define _i(name, opcode, desc) \ case name: return desc; switch (i) { AllInstructions } return "#err"; } LStream LScriptArguments::NullConsole; ////////////////////////////////////////////////////////////////////////////////////// int LScriptUtils::atoi(char16 *s) { int i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::atoi(b); } return i; } int64 LScriptUtils::atoi64(char16 *s) { int64 i = 0; if (s) { #ifdef _MSC_VER i = _wtoi64(s); #else char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = strtoll(b, 0, 10); #endif } return i; } double LScriptUtils::atof(char16 *s) { double i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::atof(b); } return i; } int LScriptUtils::htoi(char16 *s) { int i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::htoi(b); } return i; } ////////////////////////////////////////////////////////////////////////////////////// SystemFunctions::SystemFunctions() { Engine = NULL; Log = NULL; #ifdef WINNATIVE Brk = NULL; #endif } SystemFunctions::~SystemFunctions() { } LStream *SystemFunctions::GetLog() { return Log; } bool SystemFunctions::SetLog(LStream *log) { LAssert(Log == NULL); Log = log; return true; } void SystemFunctions::SetEngine(LScriptEngine *Eng) { Engine = Eng; } bool SystemFunctions::Assert(LScriptArguments &Args) { *Args.GetReturn() = true; if (Args.Length() == 0) return true; auto v = Args[0]->CastInt32(); if (!v) { const char *Msg = Args.Length() > 1 ? Args[1]->CastString() : NULL; *Args.GetReturn() = false; Args.Throw(NULL, -1, Msg); } return true; } bool SystemFunctions::DebuggerEnabled(LScriptArguments &Args) { if (Args.Length() == 0) { LAssert(!"Wrong args."); return false; } Args.GetVm()->SetDebuggerEnabled(Args[0]->CastInt32() != 0); return true; } bool SystemFunctions::Throw(LScriptArguments &Args) { const char *Msg = Args.Length() > 0 ? Args[0]->CastString() : NULL; Args.Throw(NULL, -1, Msg); return true; } bool SystemFunctions::LoadString(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } *Args.GetReturn() = LLoadString(Args[0]->CastInt32()); return true; } bool SystemFunctions::Sprintf(LScriptArguments &Args) { if (Args.Length() < 1) { LAssert(!"Wrong args."); return false; } char *Fmt = Args[0]->Str(); if (!Fmt) return false; #if defined(LINUX) || defined(MAC) // No support for sprintf with generated args... hack a string up // Formatting widths etc not supported. LArray s; int i = 1; for (char *f = Fmt; *f; f++) { if (f[0] == '%' && f[1] != '%') { f++; // Skip '%' // char *Fmt = f; while (*f && !IsAlpha(*f)) f++; // Skip formatting.. if (i >= Args.Length()) break; // No more arguments... switch (*f) { case 's': { // String... char *v = Args[i]->CastString(); if (v) s.Add(v, strlen(v)); else s.Add((char*)"(null)", 4); break; } case 'c': { char *Str = Args[i]->Str(); s.Add(Str ? *Str : '?'); break; } case 'f': case 'g': { break; } case 'u': case 'd': case 'i': { // Int... LString v; v.Printf("%i", Args[i]->CastInt32()); s.Add(v.Get(), v.Length()); break; } } i++; } else s.Add(*f); } s.Add(0); // NULL terminate *Args.GetReturn() = s.AddressOf(); #else LArray Params; va_list a; unsigned i = 1; for (char *f = Fmt; *f; f++) { if (f[0] == '%' && f[1] != '%') { char *t = f + 1; while (*t && !IsAlpha(*t)) t++; if (i >= Args.Length()) { LAssert(!"Not enough args."); break; } switch (*t) { case 's': { Params.Add((UNativeInt)Args[i++]->Str()); break; } case 'c': { char *Str = Args[i++]->Str(); Params.Add(Str ? *Str : '?'); break; } case 'f': case 'g': { union tmp { double Dbl; struct { uint32_t High; uint32_t Low; }; } Tmp; Tmp.Dbl = Args[i++]->CastDouble(); Params.Add(Tmp.High); Params.Add(Tmp.Low); break; } default: { Params.Add(Args[i++]->CastInt32()); break; } } f = *t ? t + 1 : t; } } a = (va_list) &Params[0]; #ifndef WIN32 #define _vsnprintf vsnprintf #endif char Buf[1024]; vsprintf_s(Buf, sizeof(Buf), Fmt, a); *Args.GetReturn() = Buf; #endif return true; } bool SystemFunctions::ReadTextFile(LScriptArguments &Args) { if (Args.Length() == 1 && LFileExists(Args[0]->CastString())) { if (Args.GetReturn()->OwnStr(::LReadTextFile(Args[0]->CastString()))) return true; } return false; } bool SystemFunctions::WriteTextFile(LScriptArguments &Args) { if (Args.Length() == 2) { LFile f; if (f.Open(Args[0]->CastString(), O_WRITE)) { f.SetSize(0); LVariant *v = Args[1]; if (v) { switch (v->Type) { default: break; case GV_STRING: { size_t Len = strlen(v->Value.String); *Args.GetReturn() = f.Write(v->Value.String, Len) == Len; return true; break; } case GV_BINARY: { *Args.GetReturn() = f.Write(v->Value.Binary.Data, v->Value.Binary.Length) == v->Value.Binary.Length; return true; break; } } } } } return false; } LView *SystemFunctions::CastLView(LVariant &v) { switch (v.Type) { default: break; case GV_DOM: return dynamic_cast(v.Value.Dom); case GV_GVIEW: return v.Value.View; } return 0; } bool SystemFunctions::SelectFiles(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { Args.Throw(_FL, "SelectFiles(Parent, Callback[, FileTypes[, InitialDir[, MultiSelect[, SaveAs]]]]) expects at least 2 arguments."); return false; } auto Ctx = Args.GetVm()->SaveContext(); if (!Ctx) { Args.Throw(_FL, "SelectFiles(...) requires a valid callback context."); return false; } LFileSelect *s = new LFileSelect; if (!s) return false; s->Parent(CastLView(*Args[0])); auto Callback = Args[1]->Str(); auto Types = LString(Args.IdxCheck(2) ? Args[2]->CastString() : NULL).SplitDelimit(",;:"); for (auto c: Types) { char *sp = strrchr(c, ' '); if (sp) { *sp++ = 0; s->Type(sp, c); } else { char *dot = strrchr(c, '.'); if (dot) { char Type[256]; sprintf_s(Type, sizeof(Type), "%s files", dot + 1); s->Type(Type, c); } } } s->Type("All Files", LGI_ALL_FILES); s->InitialDir (Args.IdxCheck(3) ? Args[3]->CastString() : 0); s->MultiSelect(Args.IdxCheck(4) ? Args[4]->CastInt32() != 0 : true); bool SaveAs = Args.IdxCheck(5) ? Args[5]->CastInt32() != 0 : false; auto Process = [Callback=LString(Callback),Ctx](LFileSelect *s, bool ok) { if (ok) { LScriptArguments Args(NULL); LVariant *v = new LVariant; if (auto Lst = v->SetList()) { for (unsigned i=0; iLength(); i++) { auto path = (*s)[i]; Lst->Insert(new LVariant(path)); } } Args.Add(v); Ctx.Call(Callback, Args); Args.DeleteObjects(); } delete s; }; if (SaveAs) s->Save(Process); else s->Open(Process); return true; } bool SystemFunctions::SelectFolder(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { Args.Throw(_FL, "SelectFolder(Parent, Callback[, InitialDir]) expects at least 2 arguments."); return false; } auto Ctx = Args.GetVm()->SaveContext(); if (!Ctx) { Args.Throw(_FL, "SelectFiles(...) requires a valid callback context."); return false; } LFileSelect *s = new LFileSelect; if (!s) return false; s->Parent(CastLView(*Args[0])); auto Callback = Args[1]->Str(); if (Args.IdxCheck(2)) s->InitialDir(Args[2]->CastString()); s->OpenFolder([Ctx, Callback = LString(Callback)](auto s, bool ok) { if (ok) { LScriptArguments Args(NULL); Args.Add(new LVariant(s->Name())); Ctx.Call(Callback, Args); Args.DeleteObjects(); } delete s; }); return true; } bool SystemFunctions::Sleep(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } LSleep(Args[0]->CastInt32()); return true; } bool SystemFunctions::ToString(LScriptArguments &Args) { LStringPipe p; const char *Sep = ", "; for (unsigned i=0; iToString(); p.Print("%s%s", i?Sep:"", s.Get()); } Args.GetReturn()->OwnStr(p.NewStr()); return true; } +bool SystemFunctions::Lgi4CC(LScriptArguments &Args) +{ + auto s = Args.StringAt(0); + if (!s) + return false; + + auto i = ::Lgi4CC(s); + *Args.GetReturn() = i; + return true; +} + bool SystemFunctions::Print(LScriptArguments &Args) { LStream *Out = Log ? Log : (Engine ? Engine->GetConsole() : NULL); for (unsigned n=0; Out && nWrite("NULL", 4); continue; } #if 1 size_t Len = strlen(f); Out->Write(f, Len); #else char *i = f, *o = f; for (; *i; i++) { if (*i == '\\') { i++; switch (*i) { case 'n': *o++ = '\n'; break; case 'r': *o++ = '\r'; break; case 't': *o++ = '\t'; break; case '\\': *o++ = '\\'; break; case '0': *o++ = 0; break; } } else { *o++ = *i; } } *o = 0; Out->Write(f, o - f); #endif } return true; } bool SystemFunctions::FormatSize(LScriptArguments &Args) { if (Args.Length() != 1) return false; char s[64]; LFormatSize(s, sizeof(s), Args[0]->CastInt64()); *Args.GetReturn() = s; return true; } bool SystemFunctions::ClockTick(LScriptArguments &Args) { *Args.GetReturn() = (int64)LCurrentTime(); return true; } bool SystemFunctions::Now(LScriptArguments &Args) { Args.GetReturn()->Empty(); Args.GetReturn()->Type = GV_DATETIME; Args.GetReturn()->Value.Date = new LDateTime; Args.GetReturn()->Value.Date->SetNow(); return true; } bool SystemFunctions::New(LScriptArguments &Args) { if (Args.Length() < 1 || !Args[0]) { LAssert(!"Wrong args."); return false; } Args.GetReturn()->Empty(); char *sType = Args[0]->CastString(); if (!sType) return false; if (IsDigit(*sType)) { // Binary block int Bytes = ::atoi(sType); if (!Bytes) return false; return Args.GetReturn()->SetBinary(Bytes, new char[Bytes], true); } LVariant *Ret = Args.GetReturn(); LDomProperty Type = LStringToDomProp(sType); switch (Type) { case TypeList: { Ret->SetList(); break; } case TypeHashTable: { Ret->SetHashTable(); break; } case TypeSurface: { Ret->Empty(); Ret->Type = GV_LSURFACE; if ((Ret->Value.Surface.Ptr = new LMemDC)) { Ret->Value.Surface.Ptr->IncRef(); Ret->Value.Surface.Own = true; } break; } case TypeFile: { Ret->Empty(); #if 1 Ret->Type = GV_STREAM; Ret->Value.Stream.Ptr = new LFile; if (Ret->Value.Stream.Ptr) Ret->Value.Stream.Own = true; #else Ret->Type = GV_GFILE; if ((Ret->Value.File.Ptr = new LFile)) { Ret->Value.File.Ptr->AddRef(); Ret->Value.File.Own = true; } #endif break; } case TypeDateTime: { Ret->Empty(); Ret->Type = GV_DATETIME; Ret->Value.Date = new LDateTime; break; } default: { Ret->Empty(); LCompiledCode *c = Engine ? Engine->GetCurrentCode() : NULL; if (!c) return false; LAutoWString o(Utf8ToWide(sType)); LCustomType *t = c->GetType(o); if (t) { int ArrayLength = Args.Length() > 1 ? Args[1]->CastInt32() : 1; if (ArrayLength > 0) { Ret->Type = GV_CUSTOM; Ret->Value.Custom.Dom = t; Ret->Value.Custom.Data = new uint8_t[t->Sizeof() * ArrayLength]; } } } } return true; } bool SystemFunctions::Len(LScriptArguments &Args) { size_t i = 0; for (LVariant *v: Args) { switch (v->Type) { case GV_LIST: i += v->Value.Lst->Length(); break; case GV_HASHTABLE: i += v->Value.Hash->Length(); break; case GV_BINARY: i += v->Value.Binary.Length; break; case GV_STRING: i += Strlen(v->Value.String); break; case GV_WSTRING: i += Strlen(v->Value.WString); break; default: i += 1; break; } } *Args.GetReturn() = i; return true; } bool SystemFunctions::Delete(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } LVariant *v = Args[0]; if (v->Type == GV_CUSTOM) { DeleteArray(v->Value.Custom.Data); v->Empty(); } else { v->Empty(); } *Args.GetReturn() = true; return true; } class LFileListEntry : public LDom { bool Folder; LVariant Name; int64 Size; LDateTime Modified; public: LFileListEntry(LDirectory *d) { Folder = d->IsDir(); Name = d->GetName(); Size = d->GetSize(); Modified.Set(d->GetLastWriteTime()); } bool GetVariant(const char *Var, LVariant &Value, const char *Arr = NULL) override { LDomProperty p = LStringToDomProp(Var); switch (p) { case ObjName: Value = Name; break; case ObjLength: Value = Size; break; case FileFolder: Value = Folder; break; case FileModified: Value = &Modified; break; default: return false; } return true; } }; bool SystemFunctions::DeleteFile(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } char *f = Args[0]->CastString(); if (f) *Args.GetReturn() = FileDev->Delete(Args[0]->CastString()); else *Args.GetReturn() = false; return true; } bool SystemFunctions::CurrentScript(LScriptArguments &Args) { LCompiledCode *Code; if (Engine && (Code = Engine->GetCurrentCode())) { *Args.GetReturn() = Code->GetFileName(); return true; } return false; } bool SystemFunctions::PathExists(LScriptArguments &Args) { if (Args.Length() == 0) { LAssert(!"Wrong args."); return false; } LDirectory d; if (d.First(Args[0]->CastString(), NULL)) { if (d.IsDir()) *Args.GetReturn() = 2; else *Args.GetReturn() = 1; } else { *Args.GetReturn() = 0; } return true; } bool SystemFunctions::PathJoin(LScriptArguments &Args) { char p[MAX_PATH_LEN] = ""; for (unsigned i=0; iCastString(); if (i) LMakePath(p, sizeof(p), p, s); else strcpy_s(p, sizeof(p), s); } if (*p) *Args.GetReturn() = p; else Args.GetReturn()->Empty(); return true; } bool SystemFunctions::PathSep(LScriptArguments &Args) { *Args.GetReturn() = DIR_STR; return true; } bool SystemFunctions::ListFiles(LScriptArguments &Args) { if (Args.Length() < 1) { Args.GetReturn()->Empty(); Args.Throw(_FL, "ListFiles(FolderPath[, FilterPattern]) expects at least one argument."); return false; } Args.GetReturn()->SetList(); auto Folder = Args[0]->CastString(); auto Pattern = Args.Length() > 1 ? Args[1]->CastString() : NULL; LDirectory d; for (auto b=d.First(Folder); b; b=d.Next()) { if (!Pattern || MatchStr(Pattern, d.GetName())) Args.GetReturn()->Value.Lst->Insert(new LVariant(new LFileListEntry(&d))); } return true; } bool SystemFunctions::CreateSurface(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { Args.Throw(_FL, "CreateSurface(x, y[, Bits|ColourSpace]) expects at least two arguments."); return false; } auto x = Args[0]->CastInt32(); auto y = Args[1]->CastInt32(); LColourSpace Cs = CsNone; if (Args.Length() > 2) { LVariant *Type = Args[2]; const char *c; if (Type->IsInt()) { // Bit depth... convert to default Colour Space. Cs = LBitsToColourSpace(Type->CastInt32()); } else if ((c = Type->Str())) { // Parse string colour space def Cs = LStringToColourSpace(Type->Str()); } } if (!Cs) // Catch all error cases and make it the default screen depth. Cs = GdcD->GetColourSpace(); auto r = Args.GetReturn(); if ((r->Value.Surface.Ptr = new LMemDC(x, y, Cs))) { r->Type = GV_LSURFACE; r->Value.Surface.Own = true; r->Value.Surface.Ptr->IncRef(); } return true; } bool SystemFunctions::ColourSpaceToString(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() != 1) { Args.Throw(_FL, "ColourSpaceToString(ColourSpaceInt) expects at least one argument."); return false; } *Args.GetReturn() = LColourSpaceToString((LColourSpace)Args[0]->CastInt32()); return true; } bool SystemFunctions::StringToColourSpace(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() != 1) { Args.Throw(_FL, "StringToColourSpace(ColourSpaceStr) expects at least one argument."); return false; } *Args.GetReturn() = (int)LStringToColourSpace(Args[0]->Str()); return true; } bool SystemFunctions::MessageDlg(LScriptArguments &Args) { if (Args.Length() < 2) { Args.Throw(_FL, "MessageDlg(Parent, Message[, Title[, Buttons]]) expects at least 2 arguments."); return false; } auto Parent = CastLView(*Args[0]); auto Msg = Args[1]->Str(); auto Title = Args.IdxCheck(2) ? Args[2]->Str() : LAppInst->Name(); auto Btns = Args.IdxCheck(3) ? Args[3]->CastInt32() : MB_OK; auto Btn = LgiMsg(Parent, Msg, Title, Btns); *Args.GetReturn() = Btn; return true; } bool SystemFunctions::GetInputDlg(LScriptArguments &Args) { if (Args.Length() < 5) { Args.Throw(_FL, "GetInputDlg(Parent, DefaultInput, Message, Title, IsPassword, Callback) expects 5 arguments."); return false; } auto Ctx = Args.GetVm()->SaveContext(); if (!Ctx) { Args.Throw(_FL, "GetInputDlg requires a valid VM context."); return false; } auto Parent = CastLView(*Args[0]); auto InitVal = Args[1]->Str(); auto Msg = Args[2]->Str(); auto Title = Args[3]->Str(); auto Pass = Args[4]->CastInt32() != 0; auto Callback = Args[5]->Str(); Args.GetReturn()->Empty(); auto Dlg = new LInput(Parent, InitVal, Msg, Title, Pass); Dlg->DoModal([this, Dlg, Ctx, Callback=LString(Callback)](auto d, auto ok) { if (ok) { LScriptArguments Args(NULL); Args.Add(new LVariant(Dlg->GetStr())); Ctx.Call(Callback, Args); Args.DeleteObjects(); } delete Dlg; }); // Don't wait here... return true; } bool SystemFunctions::GetViewById(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } LViewI *Parent = CastLView(*Args[0]); int Id = Args[1]->CastInt32(); if (!Parent || Id <= 0) return false; if (Parent->GetViewById(Id, Args.GetReturn()->Value.View)) { Args.GetReturn()->Type = GV_GVIEW; } return true; } bool SystemFunctions::Execute(LScriptArguments &Args) { if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } LStringPipe p; char *Exe = Args[0]->CastString(); char *Arguments = Args[1]->CastString(); LSubProcess e(Exe, Arguments); bool Status = e.Start(); if (Status) { e.Communicate(&p); LAutoString o(p.NewStr()); *Args.GetReturn() = o; } else if (Log) { uint32_t ErrCode = e.GetErrorCode(); LString ErrMsg = LErrorCodeToString(ErrCode); if (ErrMsg) Log->Print("Error: Execute(\"%s\",\"%s\") failed with '%s'\n", Exe, Arguments, ErrMsg.Get()); else Log->Print("Error: Execute(\"%s\",\"%s\") failed with '0x%x'\n", Exe, Arguments, ErrCode); } return Status; } bool SystemFunctions::System(LScriptArguments &Args) { if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } char *Exe = Args[0]->Str(); char *Arg = Args[1]->Str(); *Args.GetReturn() = LExecute(Exe, Arg); return true; } bool SystemFunctions::OsName(LScriptArguments &Args) { *Args.GetReturn() = LGetOsName(); return true; } bool SystemFunctions::OsVersion(LScriptArguments &Args) { LArray Ver; LGetOs(&Ver); Args.GetReturn()->SetList(); for (int i=0; i<3; i++) Args.GetReturn()->Value.Lst->Insert(new LVariant(Ver[i])); return true; } #define DefFn(Name) \ LHostFunc(#Name, 0, (ScriptCmd)&SystemFunctions::Name) LHostFunc SystemLibrary[] = { // Debug DefFn(Assert), DefFn(Throw), DefFn(DebuggerEnabled), // String handling DefFn(LoadString), DefFn(FormatSize), DefFn(Sprintf), DefFn(Print), DefFn(ToString), + DefFn(Lgi4CC), // Containers/objects DefFn(New), DefFn(Delete), DefFn(Len), // Files DefFn(ReadTextFile), DefFn(WriteTextFile), DefFn(SelectFiles), DefFn(SelectFolder), DefFn(ListFiles), DefFn(DeleteFile), DefFn(CurrentScript), DefFn(PathJoin), DefFn(PathExists), DefFn(PathSep), // Time DefFn(ClockTick), DefFn(Sleep), DefFn(Now), // Images DefFn(CreateSurface), DefFn(ColourSpaceToString), DefFn(StringToColourSpace), // UI DefFn(MessageDlg), DefFn(GetInputDlg), DefFn(GetViewById), // System DefFn(Execute), DefFn(System), DefFn(OsName), DefFn(OsVersion), // End of list marker LHostFunc(0, 0, 0), }; LHostFunc *SystemFunctions::GetCommands() { return SystemLibrary; } diff --git a/src/common/Coding/ScriptingPriv.h b/src/common/Coding/ScriptingPriv.h --- a/src/common/Coding/ScriptingPriv.h +++ b/src/common/Coding/ScriptingPriv.h @@ -1,591 +1,593 @@ #ifndef _GSCRIPTING_PRIV_H_ #define _GSCRIPTING_PRIV_H_ #include #include "lgi/common/Scripting.h" #include "lgi/common/RefCount.h" // Instructions #define _i(name, opcode, desc) \ name = opcode, #define AllInstructions \ _i(INop, 0, "Nop") \ _i(IAssign, OpAssign, "OpAssign") \ _i(IPlus, OpPlus, "OpPlus") \ _i(IUnaryPlus, OpUnaryPlus, "OpUnaryPlus") \ _i(IMinus, OpMinus, "OpMinus") \ _i(IUnaryMinus, OpUnaryMinus, "OpUnaryMinus") \ _i(IMul, OpMul, "OpMul") \ _i(IDiv, OpDiv, "OpDiv") \ _i(IMod, OpMod, "OpMod") \ _i(ILessThan, OpLessThan, "OpLessThan") \ _i(ILessThanEqual, OpLessThanEqual, "OpLessThanEqual") \ _i(IGreaterThan, OpGreaterThan, "OpGreaterThan") \ _i(IGreaterThanEqual, OpGreaterThanEqual, "OpGreaterThanEqual") \ _i(IEquals, OpEquals, "OpEquals") \ _i(INotEquals, OpNotEquals, "OpNotEquals") \ _i(IPlusEquals, OpPlusEquals, "OpPlusEquals") \ _i(IMinusEquals, OpMinusEquals, "OpMinusEquals") \ _i(IMulEquals, OpMulEquals, "OpMulEquals") \ _i(IDivEquals, OpDivEquals, "OpDivEquals") \ _i(IPostInc, OpPostInc, "OpPostInc") \ _i(IPostDec, OpPostDec, "OpPostDec") \ _i(IPreInc, OpPreInc, "OpPreInc") \ _i(IPreDec, OpPreDec, "OpPreDec") \ _i(IAnd, OpAnd, "OpAnd") \ _i(IOr, OpOr, "OpOr") \ _i(INot, OpNot, "OpNot") \ \ /** Calls a another part of the script */ \ _i(ICallScript, 64, "CallScript") \ /** Calls a method defined by the script context */ \ _i(ICallMethod, 65, "CallMethod") \ _i(IDomGet, 67, "DomGet") \ _i(IDomSet, 68, "DomSet") \ _i(IPush, 69, "Push") \ _i(IPop, 70, "Pop") \ _i(IJump, 71, "Jump") \ _i(IJumpZero, 72, "JumpZ") \ _i(IArrayGet, 73, "ArrayGet") \ _i(IArraySet, 74, "ArraySet") \ _i(IRet, 75, "Return") \ _i(IDomCall, 76, "DomCall") \ /* Stop in the VM at instruction */ \ _i(IBreakPoint, 77, "BreakPoint") \ _i(ICast, 78, "Cast") \ /* Open the debugger */ \ _i(IDebug, 79, "Debug") \ enum GInstruction { AllInstructions }; enum OperatorType { OpPrefix, OpInfix, OpPostfix, }; extern char16 sChar[]; extern char16 sInt[]; extern char16 sUInt[]; extern char16 sInt32[]; extern char16 sUInt32[]; extern char16 sInt64[]; extern char16 sHWND[]; extern char16 sDWORD[]; extern char16 sLPTSTR[]; extern char16 sLPCTSTR[]; extern char16 sElse[]; extern char16 sIf[]; extern char16 sFunction[]; extern char16 sExtern[]; extern char16 sFor[]; extern char16 sWhile[]; extern char16 sReturn[]; extern char16 sInclude[]; extern char16 sDefine[]; extern char16 sStruct[]; extern char16 sTrue[]; extern char16 sFalse[]; extern char16 sNull[]; extern char16 sOutParam[]; extern char16 sHash[]; extern char16 sPeriod[]; extern char16 sComma[]; extern char16 sSemiColon[]; extern char16 sStartRdBracket[]; extern char16 sEndRdBracket[]; extern char16 sStartSqBracket[]; extern char16 sEndSqBracket[]; extern char16 sStartCurlyBracket[]; extern char16 sEndCurlyBracket[]; extern const char *InstToString(GInstruction i); /* Variable Reference: Can either be a: - stack variable - global variable - register (0 .. MAX_REGISTER - 1) Thus a variable reference encodes 2 pieces of information, first the scope of the reference (as above) and secondly the index into that scope. Scopes are stored as arrays of variables. The scope is one byte, the index is 3 bytes following, totally 4 bytes per ref. */ #define MAX_REGISTER 16 /// 32bit variable reference, used to track where a variable is during compilation. struct LVarRef { /// \sa #SCOPE_REGISTER, #SCOPE_LOCAL or #SCOPE_GLOBAL unsigned Scope : 8; /// Index into scope int Index : 24; bool Valid() { return Index >= 0; } void Empty() { Scope = 0; Index = -1; } bool IsReg() { return Scope == SCOPE_REGISTER && Index >= 0 && Index < MAX_REGISTER; } void SetReg(int i) { Scope = SCOPE_REGISTER; Index = i; } bool operator ==(LVarRef &r) { return r.Scope == Scope && r.Index == Index; } bool operator !=(LVarRef &r) { return r.Scope != Scope || r.Index != Index; } const char *GetStr() { if (Index < 0) { LAssert(!"Invalid reference"); return "NoRef"; } #define GETSTR_BUF_SIZE 16 static char Buf[8][GETSTR_BUF_SIZE]; static int Cur = 0; static char Names[] = {'R', 'L', 'G'}; char *b = Buf[Cur++]; if (Cur >= 8) Cur = 0; LAssert(Scope <= SCOPE_GLOBAL); sprintf_s(b, GETSTR_BUF_SIZE, "%c%i", Names[Scope], Index); return b; } }; union LScriptPtr { uint8_t *u8; uint16 *u16; uint32_t *u32; int8 *i8; int16 *i16; int32 *i32; double *dbl; float *flt; LVarRef *r; LFunc **fn; }; class SystemFunctions; class LCompileTools { protected: OperatorType OpType(LOperator o) { switch (o) { case OpUnaryPlus: case OpUnaryMinus: case OpPreInc: case OpPreDec: case OpNot: return OpPrefix; case OpPostInc: case OpPostDec: return OpPostfix; default: return OpInfix; } } int GetPrecedence(LOperator o) { // Taken from: // http://www.cppreference.com/operator_precedence.html switch (o) { case OpAssign: case OpMinusEquals: case OpPlusEquals: case OpMulEquals: case OpDivEquals: return 16; case OpAnd: return 13; case OpOr: return 14; case OpEquals: case OpNotEquals: return 9; case OpLessThan: case OpLessThanEqual: case OpGreaterThan: case OpGreaterThanEqual: return 8; case OpPlus: case OpMinus: return 6; case OpMul: case OpDiv: case OpMod: return 5; case OpUnaryPlus: case OpUnaryMinus: case OpPreInc: case OpPreDec: case OpNot: return 3; case OpPostInc: case OpPostDec: return 2; case OpNull: return 0; default: LAssert(!"Really?"); break; } return -1; } LOperator IsOp(char16 *s, int PrevIsOp) { if (!s) return OpNull; if (s[0] != 0 && !s[1]) { // One character operator switch (*s) { case '=': return OpAssign; case '*': return OpMul; case '/': return OpDiv; case '<': return OpLessThan; case '>': return OpGreaterThan; case '%': return OpMod; case '!': return OpNot; case '+': { if (PrevIsOp == 0) return OpPlus; return OpUnaryPlus; } case '-': { if (PrevIsOp == 0) return OpMinus; return OpUnaryMinus; } } } else if (s[0] != 0 && s[1] == '=' && !s[2]) { // 2 chars, "something" equals operator switch (*s) { case '!': return OpNotEquals; case '=': return OpEquals; case '<': return OpLessThanEqual; case '>': return OpGreaterThanEqual; case '+': return OpPlusEquals; case '-': return OpMinusEquals; case '*': return OpMulEquals; case '/': return OpDivEquals; } } else if (s[0] == '+' && s[1] == '+' && !s[2]) { if (PrevIsOp == 0) return OpPostInc; return OpPreInc; } else if (s[0] == '-' && s[1] == '-' && !s[2]) { if (PrevIsOp == 0) return OpPostDec; return OpPreDec; } else if (s[0] == '&' && s[1] == '&' && !s[2]) { return OpAnd; } else if (s[0] == '|' && s[1] == '|' && !s[2]) { return OpOr; } return OpNull; } }; /// This class compiles the source down to byte code class LCompiler : public LScriptUtils { class LCompilerPriv *d; public: /// Constructor LCompiler(); ~LCompiler(); /// Compile the source into byte code. bool Compile ( LAutoPtr &Code, LScriptContext *SysContext, LScriptContext *UserContext, const char *FileName, const char *Script, LDom *Args ); }; /// This class is the VM for the byte language class LVirtualMachine : public LScriptUtils { friend class LVmDebuggerWnd; friend class LScriptArguments; class LVirtualMachinePriv *d; public: static bool BreakOnWarning; class Context { friend class LVirtualMachine; LCompiledCode *Code = NULL; LVmCallback *Callback = NULL; ssize_t Addr = -1; public: operator bool() { return Code != NULL && Callback != NULL; } bool Call(LString CallbackName, LScriptArguments &Args) const { if (!Callback) return false; LVirtualMachine Vm(*this); auto Prev = Args.GetVm(); Args.SetVm(&Vm); auto result = Callback->CallCallback(Vm, CallbackName, Args); Args.SetVm(Prev); return result; } }; LVirtualMachine(LVmCallback *callback = NULL); LVirtualMachine(Context ctx); LVirtualMachine(LVirtualMachine *vm); ~LVirtualMachine(); /// Executes the whole script starting at the top LExecutionStatus Execute ( /// [In] The code to execute LCompiledCode *Code, /// [In] The instruction to start at... [defaults to the start of script) uint32_t StartOffset = 0, /// [Optional] Log file for execution LStream *Log = NULL, /// Start the script execution straight away? bool StartImmediately = true, /// Optional return value LVariant *Return = NULL ); /// Execute just one method and return LExecutionStatus ExecuteFunction ( /// [In] The code to execute LCompiledCode *Code, /// [In] The function to execute LFunctionInfo *Func, /// [In/Out] The function's arguments LScriptArguments &Args, /// [Optional] Log file for execution LStream *Log = NULL, /// [Optional] Copy arguments back to this array LScriptArguments *ArgsOut = NULL ); // Debugging commands LVmDebugger *OpenDebugger(LCompiledCode *Code = NULL, const char *Assembly = NULL); bool StepInstruction(); bool StepLine(); bool StepOut(); bool BreakExecution(); bool Continue(); bool BreakPoint(const char *File, int Line, bool Add); bool BreakPoint(int Addr, bool Add); void SetBreakCpp(bool Brk); void SetDebuggerEnabled(bool b); // Other methods void SetTempPath(const char *Path); LVmCallback *GetCallback(); Context SaveContext(); }; /// Scripting engine system functions class SystemFunctions : public LScriptContext { LScriptEngine *Engine; LStream *Log; #ifdef WINNATIVE HANDLE Brk; #endif LView *CastLView(LVariant &v); public: SystemFunctions(); ~SystemFunctions(); LStream *GetLog() override; bool SetLog(LStream *log) override; void SetEngine(LScriptEngine *Eng); LString GetIncludeFile(const char *FileName) override { return LString(); } LHostFunc *GetCommands() override; // Debug bool Assert(LScriptArguments &Args); bool Throw(LScriptArguments &Args); bool DebuggerEnabled(LScriptArguments &Args); // String bool LoadString(LScriptArguments &Args); /// Formats a string bool Sprintf(LScriptArguments &Args); /// Formats a file size bool FormatSize(LScriptArguments &Args); /// Prints items to the console bool Print(LScriptArguments &Args); /// Converts args to string bool ToString(LScriptArguments &Args); + /// Turn a 4 char string into an int + bool Lgi4CC(LScriptArguments &Args); // Object creation/deletion bool New(LScriptArguments &Args); bool Delete(LScriptArguments &Args); bool Len(LScriptArguments &Args); // File /// Reads a text file into a variable bool ReadTextFile(LScriptArguments &Args); /// Writes a text file from a variable bool WriteTextFile(LScriptArguments &Args); /// \brief Opens a file open dialog to select files. /// /// Args: LView *Parent, char *Patterns, /// char *InitFolder, bool Multiselect bool SelectFiles(LScriptArguments &Args); /// Open a folder select dialog /// /// Args: LView *Parent, char *InitFolder bool SelectFolder(LScriptArguments &Args); /// Lists file in folder /// /// Args; char *Path, [optional] char *Pattern /// Returns: List of DOM objects with the following fields: /// Name - File/dir name /// Size - Size of entry /// Folder - bool, true if folder /// Modified - LDateTime, modified time bool ListFiles(LScriptArguments &Args); /// Deletes a file bool DeleteFile(LScriptArguments &Args); /// Gets the current script path. bool CurrentScript(LScriptArguments &Args); /// Finds out if a path exists. bool PathExists(LScriptArguments &Args); /// Joins path segments together. bool PathJoin(LScriptArguments &Args); /// Returns the current OS path separator. bool PathSep(LScriptArguments &Args); // Time /// Sleeps a number of milliseconds bool Sleep(LScriptArguments &Args); /// Get the current tick count bool ClockTick(LScriptArguments &Args); /// Get the date time bool Now(LScriptArguments &Args); // Bitmaps /// Creates a memory context bool CreateSurface(LScriptArguments &Args); bool ColourSpaceToString(LScriptArguments &Args); bool StringToColourSpace(LScriptArguments &Args); // User interface /// Standard alert message box bool MessageDlg(LScriptArguments &Args); /// Gets an input string from the user /// String GetInputDlg(Window Parent, String InitialValue, String Question, String Title, bool IsPassword, String Callback. bool GetInputDlg(LScriptArguments &Args); /// Gets a view by id bool GetViewById(LScriptArguments &Args); // System /// Executes a command, waits for it to finish, then returns it's output: /// String Execute(String Application, String CmdLine); bool Execute(LScriptArguments &Args); /// Executes a command and doesn't wait for it to return: /// Bool System(String Application, String CmdLine); bool System(LScriptArguments &Args); /// Gets the operating system name. bool OsName(LScriptArguments &Args); /// Gets the operating system version. bool OsVersion(LScriptArguments &Args); }; #endif