diff --git a/include/lgi/common/DocView.h b/include/lgi/common/DocView.h --- a/include/lgi/common/DocView.h +++ b/include/lgi/common/DocView.h @@ -1,524 +1,524 @@ /// \file /// \author Matthew Allen (fret@memecode.com) /// \brief This is the base data and code for all the text controls (inc. HTML) #pragma once #include #include "lgi/common/Variant.h" #include "lgi/common/Notifications.h" #include "lgi/common/Thread.h" #include "lgi/common/Layout.h" // Word wrap enum LDocWrapType { /// No word wrapping TEXTED_WRAP_NONE = 0, /// Dynamically wrap line to editor width TEXTED_WRAP_REFLOW = 1, }; // Util macros /// Returns true if 'c' is whitespace #define IsWhiteSpace(c) ((c) < 126 && strchr(LDocView::WhiteSpace, c) != 0) /// Returns true if 'c' is a delimiter #define IsDelimiter(c) ((c) < 126 && strchr(LDocView::Delimiters, c) != 0) /// Returns true if 'c' is a letter or number #define IsText(c) (IsDigit(c) || IsAlpha(c) || (c) == '_') /// Returns true if 'c' is word boundry #define IsWordBoundry(c) (strchr(LDocView::WhiteSpace, c) || strchr(LDocView::Delimiters, c)) /// Returns true if 'c' is alphanumeric or a digit #define AlphaOrDigit(c) (IsDigit(c) || IsAlpha(c)) /// Returns true if 'c' is a valid URL character #define UrlChar(c) ( \ strchr(LDocView::UrlDelim, (c)) || \ AlphaOrDigit((c)) || \ ((c) >= 256) \ ) /// Returns true if 'c' is email address character #define EmailChar(c) (strchr("._-:+", (c)) || AlphaOrDigit((c))) LgiFunc char16 *ConvertToCrLf(char16 *Text); /// This class contains information about a link. /// \sa LDetectLinks struct LLinkInfo { ssize_t Start; ssize_t Len; bool Email; void Set(ssize_t start, ssize_t len, bool email) { Start = start; Len = len; Email = email; } }; // Call back class to handle viewer events class LDocView; /// An environment class to handle requests from the text view to the outside world. class LgiClass LDocumentEnv : public LThreadOwner { LArray Viewers; public: LDocumentEnv(LDocView *v = 0); virtual ~LDocumentEnv(); enum LoadType { LoadError, LoadNotImpl, LoadImmediate, LoadDeferred, }; struct #ifdef MAC LgiClass #endif LoadJob : public LThreadJob { enum PrefFormat { FmtNone, FmtStream, FmtSurface, FmtFilename, }; enum JobStatus { JobInit, JobOk, JobErr_Uri, JobErr_Path, JobErr_FileOpen, JobErr_GetUri, JobErr_NoCachedFile, JobErr_ImageFilter, JobErr_NoMem, }; // View data LDocumentEnv *Env; void *UserData; uint32_t UserUid; PrefFormat Pref; // Input data LAutoString Uri; LAutoString PostData; // Output data LAutoPtr Stream; LAutoPtr pDC; LString Filename; LString Error; JobStatus Status; LString MimeType, ContentId; LoadJob(LThreadTarget *o) : LThreadJob(o) { Env = NULL; UserUid = 0; UserData = NULL; Pref = FmtNone; Status = JobInit; } LStreamI *GetStream() { if (!Stream && Filename) { LFile *file = new LFile; if (file && file->Open(Filename, O_READ)) Stream.Reset(file); else DeleteObj(file); } return Stream; } }; LoadJob *NewJob() { return new LoadJob(this); } bool AttachView(LDocView *v) { if (!v) return false; if (!Lock(_FL)) return false; LAssert(!Viewers.HasItem(v)); Viewers.Add(v); Unlock(); return true; } bool DetachView(LDocView *v) { if (!v) return false; if (!Lock(_FL)) return false; LAssert(Viewers.HasItem(v)); Viewers.Delete(v); Unlock(); return true; } int NextUid(); /// Creating a context menu, usually when the user right clicks on the /// document. virtual bool AppendItems(LSubMenu *Menu, const char *Param, int Base = 1000) { return false; } /// Do something when the menu items created by LDocumentEnv::AppendItems /// are clicked. virtual bool OnMenu(LDocView *View, int Id, void *Context) { return false; } /// Asks the env to get some data linked from the document, e.g. a css file or an iframe source etc. /// If the GetContent implementation takes ownership of the job pointer then it should set 'j' to NULL. virtual LoadType GetContent(LoadJob *&j) { return LoadNotImpl; } /// After the env's thread loads the resource it calls this to pass it to the doc void OnDone(LAutoPtr j); /// Handle a click on URI virtual bool OnNavigate(LDocView *Parent, const char *Uri) { return false; } /// Handle a form post virtual bool OnPostForm(LDocView *Parent, const char *Uri, const char *Data) { return false; } /// Process dynamic content, returning a dynamically allocated string /// for the result of the executed script. Dynamic content is enclosed /// between <? and ?>. virtual LString OnDynamicContent(LDocView *Parent, const char *Code) { return LString(); } /// Some script was received, the owner should compile it virtual bool OnCompileScript(LDocView *Parent, char *Script, const char *Language, const char *MimeType) { return false; } /// Some script needs to be executed, the owner should compile it virtual bool OnExecuteScript(LDocView *Parent, char *Script) { return false; } }; /// Default text view environment /// /// This class defines the default behavior of the environment, /// However you will need to instantiate this yourself and call /// SetEnv with your instance. i.e. it's not automatic. class LgiClass LDefaultDocumentEnv : public LDocumentEnv { public: LoadType GetContent(LoadJob *&j); bool OnNavigate(LDocView *Parent, const char *Uri); }; /// Find params class LDocFindReplaceParams { public: virtual ~LDocFindReplaceParams() {} }; /// TextView class is a base for all text controls class LgiClass LDocView : public LLayout, virtual public LDom { friend class LDocumentEnv; protected: LDocumentEnv *Environment = NULL; LString Charset; public: // Static static const char *WhiteSpace; static const char *Delimiters; static const char *UrlDelim; /////////////////////////////////////////////////////////////////////// // Properties - #define _TvMenuProp(Type, Name, Default) \ + #define DocViewProp(Type, Name, Default) \ protected: \ Type Name = Default; \ public: \ virtual void Set##Name(Type i) { Name=i; } \ Type Get##Name() { return Name; } - _TvMenuProp(uint16, WrapAtCol, 0) - _TvMenuProp(bool, UrlDetect, true) - _TvMenuProp(bool, ReadOnly, false) - _TvMenuProp(LDocWrapType, WrapType, TEXTED_WRAP_REFLOW) - _TvMenuProp(uint8_t, TabSize, 4) - _TvMenuProp(uint8_t, IndentSize, 4) - _TvMenuProp(bool, HardTabs, true) - _TvMenuProp(bool, ShowWhiteSpace, false) - _TvMenuProp(bool, ObscurePassword, false) - _TvMenuProp(bool, CrLf, false) - _TvMenuProp(bool, AutoIndent, true) - _TvMenuProp(bool, FixedWidthFont, false) - _TvMenuProp(bool, LoadImages, false) - _TvMenuProp(bool, OverideDocCharset, false) + DocViewProp(uint16, WrapAtCol, 0) + DocViewProp(bool, UrlDetect, true) + DocViewProp(bool, ReadOnly, false) + DocViewProp(LDocWrapType, WrapType, TEXTED_WRAP_REFLOW) + DocViewProp(uint8_t, TabSize, 4) + DocViewProp(uint8_t, IndentSize, 4) + DocViewProp(bool, HardTabs, true) + DocViewProp(bool, ShowWhiteSpace, false) + DocViewProp(bool, ObscurePassword, false) + DocViewProp(bool, CrLf, false) + DocViewProp(bool, AutoIndent, true) + DocViewProp(bool, FixedWidthFont, false) + DocViewProp(bool, LoadImages, false) + DocViewProp(bool, OverideDocCharset, false) // This UID is used to match data load events with their source document. // Sometimes data will arrive after the document that asked for it has // already been unloaded. So by assigned each document an UID we can check // the job UID against it and discard old data. - _TvMenuProp(int, DocumentUid, 0) - #undef _TvMenuProp + DocViewProp(int, DocumentUid, 0) + #undef DocViewProp virtual const char *GetCharset() { return Charset.Get() ? Charset.Get() : "utf-8"; } virtual void SetCharset(const char *s) { Charset = s; } virtual const char *GetMimeType() = 0; /////////////////////////////////////////////////////////////////////// // Object LDocView(LDocumentEnv *e = NULL) { SetEnv(e); } virtual ~LDocView() { SetEnv(NULL); } const char *GetClass() { return "LDocView"; } /// Open a file handler virtual bool Open(const char *Name, const char *Cs = 0) { return false; } /// Save a file handler virtual bool Save(const char *Name, const char *Cs = 0) { return false; } /////////////////////////////////////////////////////////////////////// /// Find window handler virtual void DoFind(std::function Callback) { if (Callback) Callback(false); } /// Replace window handler virtual void DoReplace(std::function Callback) { if (Callback) Callback(false); } virtual LDocFindReplaceParams *CreateFindReplaceParams() { return 0; } virtual void SetFindReplaceParams(LDocFindReplaceParams *Params) { } /////////////////////////////////////////////////////////////////////// /// Get the current environment virtual LDocumentEnv *GetEnv() { return Environment; } /// Set the current environment virtual void SetEnv(LDocumentEnv *e) { if (Environment) Environment->DetachView(this); Environment = e; if (Environment) Environment->AttachView(this); } /// When the env has loaded a resource it can pass it to the doc control via this method. /// It MUST be thread safe. Often an environment will call this function directly from /// it's worker thread. virtual void OnContent(LDocumentEnv::LoadJob *Res) {} /////////////////////////////////////////////////////////////////////// // State / Selection /// Set the cursor position, to select an area, move the cursor with Select=false /// then set the other end of the region with Select=true. virtual void SetCaret(size_t i, bool Select, bool ForceFullUpdate = false) {} /// Cursor=false means the other end of the selection if any. The cursor is alwasy /// at one end of the selection. virtual ssize_t GetCaret(bool Cursor = true) { return 0; } /// True if there is a selection virtual bool HasSelection() { return false; } /// Unselect all the text virtual void UnSelectAll() {} /// Select the word from index 'From' virtual void SelectWord(size_t From) {} /// Select all the text in the control virtual void SelectAll() {} /// Get the selection as a dynamicially allocated utf-8 string virtual char *GetSelection() { return 0; } /// Returns the character index at the x,y location virtual ssize_t IndexAt(int x, int y) { return 0; } /// Index=-1 returns the x,y of the cursor, Index >=0 returns the specified x,y virtual bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1) { return false; } /// True if the document has changed virtual bool IsDirty() { return false; } /// Gets the number of lines of text virtual size_t GetLines() { return 0; } /// Gets the pixels required to display all the text virtual void GetTextExtent(int &x, int &y) {} /////////////////////////////////////////////////////////////////////// /// Cuts the selection from the document and puts it on the clipboard virtual bool Cut() { return false; } /// Copies the selection from the document to the clipboard virtual bool Copy() { return false; } /// Pastes the current contents of the clipboard into the document virtual bool Paste() { return false; } /////////////////////////////////////////////////////////////////////// /// Called when the user hits the escape key virtual void OnEscape(LKey &K) {} /// Called when the user hits the enter key virtual void OnEnter(LKey &k) {} /// Called when the user clicks a URL virtual void OnUrl(char *Url) {} /// Called to add styling to the document virtual void OnAddStyle(const char *MimeType, const char *Styles) {} /////////////////////////////////////////////////////////////////////// struct ContentMedia { LString Id; LString FileName; LString MimeType; LVariant Data; LAutoPtr Stream; bool Valid() { return MimeType.Get() != NULL && FileName.Get() != NULL && ( (Data.Type == GV_BINARY && Data.Value.Binary.Data != NULL) || (Stream.Get() != NULL) ); } }; /// Gets the document in format of a desired MIME type virtual bool GetFormattedContent ( /// [In] The desired mime type of the content const char *MimeType, /// [Out] The content in the specified mime type LString &Out, /// [Out/Optional] Any attached media files that the content references LArray *Media = NULL ) { return false; } }; /// Detects links in text, returning their location and type template bool LDetectLinks(LArray &Links, T *Text, ssize_t TextCharLen = -1) { if (!Text) return false; if (TextCharLen < 0) TextCharLen = Strlen(Text); T *End = Text + TextCharLen; static T Http[] = {'h', 't', 't', 'p', ':', '/', '/', 0 }; static T Https[] = {'h', 't', 't', 'p', 's', ':', '/', '/', 0}; for (int64 i=0; i= 7 && ( Strnicmp(Text+i, Http, 6) == 0 || Strnicmp(Text+i, Https, 7) == 0 ) ) { // find end T *s = Text + i; T *e = s + 6; for ( ; e < End && UrlChar(*e); e++) ; while ( e > s && ! ( IsAlpha(e[-1]) || IsDigit(e[-1]) || e[-1] == '/' ) ) e--; Links.New().Set(s - Text, e - s, false); i = e - Text; } break; } case '@': { // find start T *s = Text + (MAX(i, 1) - 1); for ( ; s > Text && EmailChar(*s); s--) ; if (s < Text + i) { if (!EmailChar(*s)) s++; bool FoundDot = false; T *Start = Text + i + 1; T *e = Start; for ( ; e < End && EmailChar(*e); e++) { if (*e == '.') FoundDot = true; } while (e > Start && e[-1] == '.') e--; if (FoundDot) { Links.New().Set(s - Text, e - s, true); i = e - Text; } } break; } } } return true; } diff --git a/include/lgi/common/Scripting.h b/include/lgi/common/Scripting.h --- a/include/lgi/common/Scripting.h +++ b/include/lgi/common/Scripting.h @@ -1,456 +1,455 @@ /// \file #ifndef _LGI_SCRIPTING_H_ #define _LGI_SCRIPTING_H_ #include "lgi/common/Variant.h" #include "lgi/common/List.h" class LScriptContext; class LScriptEnginePrivate; class LVmCallback; class LVirtualMachine; typedef bool (LScriptContext::*ScriptCmd)(LScriptArguments &Args); #define SCOPE_REGISTER 0 #define SCOPE_LOCAL 1 #define SCOPE_GLOBAL 2 #define SCOPE_OBJECT 3 #define SCOPE_RETURN 4 #define SCOPE_MAX 5 /// Execution status enum LExecutionStatus { ScriptNotStarted, ScriptError, ScriptWarning, ScriptSuccess, }; /// Various type of methods enum LFuncType { /// No method type. NullFunc, /// This method is provided by the hosting application. HostFunc, /// This method is defined in the script itself. ScriptFunc, /// This method is defined in an external library. ExternFunc }; struct LFunc { LFuncType Type; LString Method; bool InUse; LFunc(const char *m = 0, LFuncType t = NullFunc) { Type = t; Method = m; InUse = false; // LStackTrace("%p alloc\n", this); } virtual ~LFunc() { // LAssert(!InUse); } virtual LExecutionStatus Call(LScriptContext *Ctx, LScriptArguments &Args) = 0; }; struct LHostFunc : public LFunc { LScriptContext *Context; LString Args; ScriptCmd Func; LHostFunc(const LHostFunc &f) { Context = f.Context; Args = f.Args; Method = f.Method; Func = f.Func; } LHostFunc(const char *method, const char *args, ScriptCmd proc) : LFunc(method, HostFunc) { Args = args; Func = proc; } LExecutionStatus Call(LScriptContext *Ctx, LScriptArguments &Args) override; }; struct LExternFunc : public LFunc { struct ExternType { int Ptr; bool Unsigned; bool Out; int ArrayLen; LVariantType Base; }; LAutoString Lib; ExternType ReturnType; LArray ArgType; LExecutionStatus Call(LScriptContext *Ctx, LScriptArguments &Args) override; }; class LFunctionInfo : public LRefCount { static int _Infos; int32 StartAddr = INVALID_ADDR; LString Name; // The reason why this is a pointer is because during the function compilation the frame // size is actually unknown. If the function calls itself then it won't know what // frame size to insert in the assembly (this is NULL). // In which case it has to insert a post compilation fix-up for the frame size. LAutoPtr FrameSize; // The number and names of the parameters to the function. LArray Params; public: static constexpr int32 INVALID_ADDR = -1; LFunctionInfo(const char *name) { if (name) Name = name; } ~LFunctionInfo() { } char *GetName() { return Name; } LArray &GetParams() { return Params; } bool ValidFrameSize() { return FrameSize.Get() != NULL; } uint16 GetFrameSize() { if (!FrameSize) { LAssert(!"Invalid frame size"); return 0; } return *FrameSize; } template bool SetFrameSize(T size) { if (size >= 0xffff) { LAssert(!"Invalid frame size."); return false; } return FrameSize.Reset(new uint16((uint16)size)); } bool ValidStartAddr() { return StartAddr != INVALID_ADDR; } int32 GetStartAddr() { LAssert(ValidStartAddr()); return StartAddr; } bool SetStartAddr(int32_t addr) { if (addr < 0) { LAssert(!"Invalid start address"); return false; } StartAddr = addr; return true; } LFunctionInfo &operator =(LFunctionInfo &f) { StartAddr = f.StartAddr; FrameSize = f.FrameSize; Name = f.Name; for (unsigned i=0; i { friend class LVirtualMachinePriv; LHashTbl,int> Lut; public: int Scope; int NullIndex; LCustomType *Obj; LVariables(int scope) { Scope = scope; NullIndex = -1; Obj = NULL; } LVariables(LCustomType *obj) { Scope = SCOPE_OBJECT; NullIndex = -1; Obj = obj; } int Var(const char *n, bool create = false) { if (Obj) { return Obj->IndexOf(n); } else { int p = Lut.Find(n); if (p) { return p - 1; } if (create) { int Len = (int)Length(); Lut.Add(n, Len + 1); Length(Len + 1); return Len; } } return -1; } }; /// A block of compile byte code class LCompiledCode { friend class LCompilerPriv; friend class LVirtualMachine; friend class LVirtualMachinePriv; friend class LCompiler; friend class LVmDebuggerWnd; /// The global variables LVariables Globals; /// The byte code of all the instructions LArray ByteCode; /// All the methods defined in the byte code and their arguments. LArray< LAutoRefPtr > Methods; /// All the externs defined in the code. LArray Externs; /// All the user types defined LHashTbl, class LCustomType*> Types; /// The original script details LString FileName; LString Source; /// The system context (all the system functions) LScriptContext *SysContext; /// Any user context (application functions) LScriptContext *UserContext; /// Debug info to map instruction address back to source line numbers LHashTbl, int> Debug; public: LCompiledCode(); LCompiledCode(LCompiledCode ©); ~LCompiledCode(); /// Size of the byte code size_t Length() { return ByteCode.Length(); } /// Assignment operator LCompiledCode &operator =(const LCompiledCode &c); /// Gets a method defined in the code LFunctionInfo *GetMethod(const char *Name, bool Create = false); /// Sets a global variable LVariant *Set(const char *Name, LVariant &v); /// Gets the definition of a struct or custom type LCustomType *GetType(char16 *Name) { return Types.Find(Name); } /// Gets the file name this code was compiled from const char *GetFileName() { return FileName; } /// Gets the source const char *GetSource() { return Source; } /// Gets the source line number associated with an address int ObjectToSourceAddress(size_t ObjAddr); /// Turns an object address into a FileName:LineNumber string. const char *AddrToSourceRef(size_t ObjAddr); /// Sets the file name this code was compiled from void SetSource(const char *file, const char *src) { if (file != FileName.Get()) FileName = file; Source = src; } }; /// New compiler/byte code/VM scripting engine class LScriptEngine { class LScriptEnginePrivate *d; public: LScriptEngine(LViewI *parent, LScriptContext *UserContext, LVmCallback *Callback); ~LScriptEngine(); LStream *GetConsole(); bool SetConsole(LStream *t); LCompiledCode *GetCurrentCode(); bool Compile( LAutoPtr &Obj, LScriptContext *UserContext, const char *Script, const char *FileName = NULL, LDom *Args = NULL); LExecutionStatus Run(LCompiledCode *Obj, LVariant *Ret = NULL, const char *TempPath = NULL); LExecutionStatus RunTemporary(LCompiledCode *Obj, char *Script, LVariant *Ret = NULL); bool EvaluateExpression(LVariant *Result, LDom *VariableSource, const char *Expression); bool CallMethod(LCompiledCode *Obj, const char *Method, LScriptArguments &Args); LScriptContext *GetSystemContext(); }; class LVirtualMachine; class LVmDebugger : public LDom { public: - /// Set the VM ownership flag. - virtual void OwnVm(bool Own) = 0; /// Makes the debugger the owner of the compiled code - virtual void OwnCompiledCode(LAutoPtr Cc) = 0; + virtual void SetCode(LAutoPtr Cc) = 0; /// Gets the code owned by the debugger virtual LCompiledCode *GetCode() = 0; /// Set the source and asm virtual void SetSource(const char *Mixed) = 0; /// Show UI and wait for user response virtual void Run() = 0; // Events /// Called to update the debugger UI to the new current execution position virtual void OnAddress(size_t Addr) = 0; /// Called when an error occurs executing the script virtual void OnError(const char *Msg) = 0; /// Called when execution starts or ends virtual void OnRun(bool Running) = 0; }; class LVmCallback : public LDom { public: /// Call a callback by name. virtual bool CallCallback(LVirtualMachine &Vm, LString CallbackName, LScriptArguments &Args) = 0; - /// Start a debugger instance to handle the execution in 'Vm' + /// Start a debugger instance to handle the execution in the Vm. + /// Implementors should COPY the Vm and Code objects to give to the debugger, because their state + /// will go away when the stack unwinds. The copy will maintain the state for the debugger to inspect. virtual LVmDebugger *AttachVm(LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) = 0; /// Compile a new script virtual bool CompileScript(LAutoPtr &Output, const char *FileName, const char *Source) = 0; }; /// Debugger for vm script class LVmDebuggerWnd : public LWindow, public LVmDebugger { struct LScriptVmDebuggerPriv *d; void UpdateVariables(LList *Lst, LVariant *Arr, ssize_t Len, char Prefix); public: - LVmDebuggerWnd(LView *Parent, LVmCallback *Callback, LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly); + LVmDebuggerWnd(LView *Parent, LVmCallback *Callback, LAutoPtr Vm, LAutoPtr Code, const char *Assembly); ~LVmDebuggerWnd(); - void OwnVm(bool Own); void OnAddress(size_t Addr); void OnError(const char *Msg); void OnRun(bool Running); void SetSource(const char *Mixed); int OnNotify(LViewI *Ctrl, LNotification n); int OnCommand(int Cmd, int Event, OsView Wnd); bool OnRequestClose(bool OsShuttingDown); LMessage::Param OnEvent(LMessage *Msg); void LoadFile(const char *File); LStream *GetLog(); - void OwnCompiledCode(LAutoPtr Cc); + void SetCode(LAutoPtr Cc); LCompiledCode *GetCode(); void Run(); }; #endif diff --git a/include/lgi/common/Variant.h b/include/lgi/common/Variant.h --- a/include/lgi/common/Variant.h +++ b/include/lgi/common/Variant.h @@ -1,467 +1,473 @@ /** \file \author Matthew Allen \brief Variant class.\n Copyright (C), Matthew Allen */ #ifndef __LVariant_H__ #define __LVariant_H__ #undef Bool #include "lgi/common/DateTime.h" #include "lgi/common/Containers.h" #include "lgi/common/HashTable.h" #include "lgi/common/LgiString.h" class LCompiledCode; #if !defined(_MSC_VER) && !defined(LINUX) && (defined(LGI_64BIT) || defined(MAC)) && !defined(HAIKU) #define LVARIANT_SIZET 1 #define LVARIANT_SSIZET 1 #endif /// The different types the varient can be. /// \sa LVariant::TypeToString to convert to string. enum LVariantType { // Main types /// Null type GV_NULL, /// 32-bit integer GV_INT32, /// 64-bit integer GV_INT64, /// true or false boolean. GV_BOOL, /// C++ double GV_DOUBLE, /// Null terminated string value GV_STRING, /// Block of binary data GV_BINARY, /// List of LVariant GV_LIST, /// Pointer to LDom object GV_DOM, /// DOM reference, ie. a variable in a DOM object GV_DOMREF, /// Untyped pointer GV_VOID_PTR, /// LDateTime class. GV_DATETIME, /// Hash table class, containing pointers to LVariants GV_HASHTABLE, // Scripting language operator GV_OPERATOR, // Custom scripting lang type GV_CUSTOM, // Wide string GV_WSTRING, // LSurface ptr GV_LSURFACE, /// Pointer to LView GV_GVIEW, /// Pointer to LMouse GV_LMOUSE, /// Pointer to LKey GV_LKEY, /// Pointer to LStream GV_STREAM, /// The maximum value for the variant type. /// (This is used by the scripting engine to refer to a LVariant itself) GV_MAX, }; /// Language operators enum LOperator { OpNull, OpAssign, OpPlus, OpUnaryPlus, OpMinus, OpUnaryMinus, OpMul, OpDiv, OpMod, OpLessThan, OpLessThanEqual, OpGreaterThan, OpGreaterThanEqual, OpEquals, OpNotEquals, OpPlusEquals, OpMinusEquals, OpMulEquals, OpDivEquals, OpPostInc, OpPostDec, OpPreInc, OpPreDec, OpAnd, OpOr, OpNot, }; class LgiClass LCustomType : public LDom { protected: struct CustomField : public LDom { ssize_t Offset; ssize_t Bytes; ssize_t ArrayLen; LVariantType Type; LString Name; LCustomType *Nested; ssize_t Sizeof(); bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); }; public: struct Method : public LDom { LString Name; LArray Params; size_t Address; int FrameSize; Method() { Address = -1; FrameSize = -1; } }; protected: // Global vars int Pack; size_t Size; LString Name; // Fields LArray Flds; LHashTbl, int> FldMap; // Methods LArray Methods; LHashTbl, Method*> MethodMap; // Private methods ssize_t PadSize(); public: LCustomType(const char *name, int pack = 1); LCustomType(const char16 *name, int pack = 1); ~LCustomType(); size_t Sizeof(); const char *GetName() { return Name; } ssize_t Members() { return Flds.Length(); } int AddressOf(const char *Field); int IndexOf(const char *Field); bool DefineField(const char *Name, LVariantType Type, int Bytes, int ArrayLen = 1); bool DefineField(const char *Name, LCustomType *Type, int ArrayLen = 1); Method *DefineMethod(const char *Name, LArray &Params, size_t Address); Method *GetMethod(const char *Name); // Field access. You can't use the LDom interface to get/set member variables because // there is no provision for the 'This' pointer. bool Get(int Index, LVariant &Out, uint8_t *This, int ArrayIndex = 0); bool Set(int Index, LVariant &In, uint8_t *This, int ArrayIndex = 0); // Dom access. However the DOM can be used to access information about the type itself. // Which doesn't need a 'This' pointer. bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool CallMethod(const char *MethodName, LScriptArguments &Args); }; /// A class that can be different types class LgiClass LVariant { public: typedef LHashTbl,LVariant*> LHash; /// The type of the variant LVariantType Type; /// The value of the variant union { /// Valid when Type == #GV_INT32 int Int; /// Valid when Type == #GV_BOOL bool Bool; /// Valid when Type == #GV_INT64 int64 Int64; /// Valid when Type == #GV_DOUBLE double Dbl; /// Valid when Type == #GV_STRING char *String; /// Valid when Type == #GV_WSTRING char16 *WString; /// Valid when Type == #GV_DOM LDom *Dom; /// Valid when Type is #GV_VOID_PTR, #GV_GVIEW, #GV_LMOUSE or #GV_LKEY void *Ptr; /// Valid when Type == #GV_BINARY struct _Binary { ssize_t Length; void *Data; } Binary; /// Valid when Type == #GV_LIST List *Lst; /// Valid when Type == #GV_HASHTABLE LHash *Hash; /// Valid when Type == #GV_DATETIME LDateTime *Date; /// Valid when Type == #GV_CUSTOM struct _Custom { LCustomType *Dom; uint8_t *Data; bool operator == (_Custom &c) { return Dom == c.Dom && Data == c.Data; } } Custom; /// Valid when Type == #GV_DOMREF struct _DomRef { /// The pointer to the dom object LDom *Dom; /// The name of the variable to set/get in the dom object char *Name; } DomRef; /// Valid when Type == #GV_OPERATOR LOperator Op; /// Valid when Type == #GV_LSURFACE struct { class LSurface *Ptr; bool Own; LSurface *Release() { auto p = Ptr; Ptr = NULL; Own = false; return p; } } Surface; /// Valid when Type == #GV_STREAM struct { class LStreamI *Ptr; bool Own; LStreamI *Release() { auto p = Ptr; Ptr = NULL; Own = false; return p; } } Stream; /// Valid when Type == #GV_GVIEW class LView *View; /// Valid when Type == #GV_LMOUSE class LMouse *Mouse; /// Valid when Type == #GV_LKEY class LKey *Key; } Value; /// Constructor to null LVariant(); /// Constructor for integers LVariant(int32_t i); LVariant(uint32_t i); LVariant(int64_t i); LVariant(uint64_t i); #if LVARIANT_SIZET LVariant(size_t i); #endif #if LVARIANT_SSIZET LVariant(ssize_t i); #endif /// Constructor for double LVariant(double i); /// Constructor for string LVariant(const char *s); /// Constructor for wide string LVariant(const char16 *s); /// Constructor for ptr LVariant(void *p); /// Constructor for DOM ptr LVariant(LDom *p); /// Constructor for DOM variable reference LVariant(LDom *p, char *name); /// Constructor for date LVariant(const LDateTime *d); /// Constructor for variant LVariant(LVariant const &v); /// Constructor for operator LVariant(LOperator Op); /// Destructor ~LVariant(); /// Assign bool value LVariant &operator =(bool i); /// Assign an integer value LVariant &operator =(int32_t i); LVariant &operator =(uint32_t i); LVariant &operator =(int64_t i); LVariant &operator =(uint64_t i); #if LVARIANT_SIZET LVariant &operator =(size_t i); #endif #if LVARIANT_SSIZET LVariant &operator =(ssize_t i); #endif /// Assign double value LVariant &operator =(double i); /// Assign string value (makes a copy) LVariant &operator =(const char *s); /// Assign a wide string value (makes a copy) LVariant &operator =(const char16 *s); /// Assign another variant value LVariant &operator =(LVariant const &i); /// Assign value to a void ptr LVariant &operator =(void *p); /// Assign value to DOM ptr LVariant &operator =(LDom *p); /// Assign value to be a date/time LVariant &operator =(const LDateTime *d); LVariant &operator =(class LView *p); LVariant &operator =(class LMouse *p); LVariant &operator =(class LKey *k); LVariant &operator =(class LStream *s); bool operator ==(LVariant &v); bool operator !=(LVariant &v) { return !(*this == v); } /// Sets the value to a DOM variable reference bool SetDomRef(LDom *obj, char *name); /// Sets the value to a copy of block of binary data bool SetBinary(ssize_t Len, void *Data, bool Own = false); /// Sets the value to a copy of the list List *SetList(List *Lst = NULL); /// Sets the value to a hashtable bool SetHashTable(LHash *Table = NULL, bool Copy = true); /// Set the value to a surface bool SetSurface(class LSurface *Ptr, bool Own); /// Set the value to a stream bool SetStream(class LStreamI *Ptr, bool Own); /// Returns the string if valid (will convert a GV_WSTRING to utf) char *Str(); /// Returns the value as an LString LString LStr(); /// Returns a wide string if valid (will convert a GV_STRING to wide) char16 *WStr(); /// Returns the string, releasing ownership of the memory to caller and /// changing this LVariant to GV_NULL. char *ReleaseStr(); /// Returns the wide string, releasing ownership of the memory to caller and /// changing this LVariant to GV_NULL. char16 *ReleaseWStr(); /// Sets the variant to a heap string and takes ownership of it bool OwnStr(char *s); /// Sets the variant to a wide heap string and takes ownership of it bool OwnStr(char16 *s); /// Sets the variant to NULL void Empty(); /// Returns the byte length of the data int64 Length(); /// True if currently a int bool IsInt(); /// True if currently a bool bool IsBool(); /// True if currently a double bool IsDouble(); /// True if currently a string bool IsString(); /// True if currently a binary block bool IsBinary(); /// True if currently null bool IsNull(); /// Changes the variant's type, maintaining the value where possible. If /// no conversion is available then nothing happens. LVariant &Cast(LVariantType NewType); /// Casts the value to int, from whatever source type. The /// LVariant type does not change after calling this. int32 CastInt32() const; /// Casts the value to a 64 bit int, from whatever source type. The /// LVariant type does not change after calling this. int64 CastInt64() const; /// Casts the value to double, from whatever source type. The /// LVariant type does not change after calling this. double CastDouble() const; /// Cast to a string from whatever source type, the LVariant will /// take the type GV_STRING after calling this. This is because /// returning a static string is not thread safe. char *CastString(); /// Casts to a DOM ptr LDom *CastDom() const; /// Casts to a boolean. You probably DON'T want to use this function. The /// behaviour for strings -> bool is such that if the string is value it /// always evaluates to true, and false if it's not a valid string. Commonly /// what you want is to evaluate whether the string is zero or non-zero in /// which cast you should use "CastInt32() != 0" instead. bool CastBool() const; /// Returns the pointer if available. void *CastVoidPtr() const; /// Returns a LView LView *CastView() const { return Type == GV_GVIEW ? Value.View : NULL; } /// List insert bool Add(LVariant *v, int Where = -1); /// Converts the varient type to a string static const char *TypeToString(LVariantType t); /// Converts an operator to a string static const char *OperatorToString(LOperator op); /// Converts the value to a string description include type. LString ToString(); }; // General collection of arguments and a return value class LgiClass LScriptArguments : public LArray { friend class LScriptEngine; friend class LVirtualMachine; friend class LVirtualMachinePriv; + friend struct ExecuteFunctionState; LVirtualMachineI *Vm = NULL; class LStream *Console = NULL; LVariant *LocalReturn = NULL; // Owned by this instance LVariant *Return = NULL; + const char *File = NULL; + int Line = 0; + LString ExceptionMsg; + public: static LStream NullConsole; LScriptArguments(LVirtualMachineI *vm, LVariant *ret = NULL, LStream *console = NULL); ~LScriptArguments(); LVirtualMachineI *GetVm() { return Vm; } void SetVm(LVirtualMachineI *vm) { Vm = vm; } LVariant *GetReturn() { return Return; } // Must never be NULL. LStream *GetConsole() { return Console; } + bool HasException() { return File != NULL || ExceptionMsg.Get() || Line != 0; } bool Throw(const char *File, int Line, const char *Msg, ...); // Accessor shortcuts const char *StringAt(size_t i); int32_t Int32At(size_t i, int32_t Default = 0); int64_t Int64At(size_t i, int64_t Default = 0); double DoubleAt(size_t i, double Default = 0); }; #endif diff --git a/src/common/Coding/Instructions.h b/src/common/Coding/Instructions.h --- a/src/common/Coding/Instructions.h +++ b/src/common/Coding/Instructions.h @@ -1,2041 +1,2045 @@ /* This file is included in both the LVirtualMachinePriv::Execute and LVirtualMachinePriv::Decompile That way the "parsing" of instructions is the same. During decompile the define VM_DECOMP is active. During execution the define VM_EXECUTE is active. */ #ifdef VM_EXECUTE #define Resolve() &Scope[c.r->Scope][c.r->Index]; c.r++ -#define GResolveRef(nm) LVariant *nm = +#define LResolveRef(nm) LVariant *nm = #else #define Resolve() c.r++ -#define GResolveRef(nm) +#define LResolveRef(nm) // LVarRef * #endif default: { #if VM_DECOMP if (Log) Log->Print("\t%p Unknown instruction %i (0x%x)\n", CurrentScriptAddress - 1, c.u8[-1], c.u8[-1]); #endif OnException(_FL, CurrentScriptAddress, "Unknown instruction"); SetScriptError; break; } case INop: { #if VM_DECOMP if (Log) Log->Print("%p Nop\n", CurrentScriptAddress - 1); #endif break; } case ICast: { #if VM_DECOMP if (Log) Log->Print("%p Cast %s", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif - GResolveRef(Var) Resolve(); + LResolveRef(Var) Resolve(); uint8_t Type = *c.u8++; #if VM_DECOMP if (Log) Log->Print(" to %s\n", LVariant::TypeToString((LVariantType)Type)); #endif #if VM_EXECUTE switch (Type) { case GV_INT32: { *Var = Var->CastInt32(); break; } case GV_STRING: { *Var = Var->CastString(); break; } case GV_DOM: { *Var = Var->CastDom(); break; } case GV_DOUBLE: { *Var = Var->CastDouble(); break; } case GV_INT64: { *Var = Var->CastInt32(); break; } case GV_BOOL: { *Var = Var->CastBool(); break; } default: { LString s; s.Printf("%s ICast warning: unknown type %i/%s\n", Code->AddrToSourceRef(CurrentScriptAddress), Var->Type, LVariant::TypeToString(Var->Type)); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IAssign: { #if VM_DECOMP if (Log) Log->Print("%p Assign %s <- %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE CheckParam(Dst != Src); *Dst = *Src; #endif break; } case IJump: { #if VM_DECOMP if (Log) Log->Print("%p Jump by %i (to 0x%x)\n", CurrentScriptAddress - 1, c.i32[0], CurrentScriptAddress + 4 + c.i32[0]); #endif #ifdef VM_EXECUTE int32 Jmp = * #endif c.i32++; #ifdef VM_EXECUTE CheckParam(Jmp != 0); c.u8 += Jmp; #endif break; } case IJumpZero: { #if VM_DECOMP if (Log) Log->Print("%p JumpZ(%s) by 0x%x\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.i32[1]); #endif - GResolveRef(Exp) Resolve(); + LResolveRef(Exp) Resolve(); #ifdef VM_EXECUTE int32 Jmp = * #endif c.i32++; #ifdef VM_EXECUTE CheckParam(Jmp != 0); if (!Exp->CastInt32()) c.u8 += Jmp; #endif break; } // case IUnaryPlus: case IUnaryMinus: { #if VM_DECOMP if (Log) Log->Print("%p UnaryMinus %s\n", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif - GResolveRef(Var) Resolve(); + LResolveRef(Var) Resolve(); #ifdef VM_EXECUTE switch (Var->Type) { case GV_DOUBLE: *Var = -Var->CastDouble(); break; case GV_INT64: *Var = -Var->CastInt64(); break; default: *Var = -Var->CastInt32(); break; } #endif break; } case IPlus: case IPlusEquals: { #if VM_DECOMP if (Log) Log->Print("%p Plus %s += %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE if (Dst->Str()) { size_t dlen = strlen(Dst->Str()); char *ss; LVariant SrcTmp; switch (Src->Type) { case GV_NULL: ss = (char*)"(null)"; break; case GV_STRING: ss = Src->Str(); break; default: SrcTmp = *Src; ss = SrcTmp.CastString(); break; } if (ss) { size_t slen = strlen(ss); char *s = new char[slen + dlen + 1]; if (s) { memcpy(s, Dst->Value.String, dlen); memcpy(s + dlen, ss, slen); s[dlen + slen] = 0; DeleteArray(Dst->Value.String); Dst->Value.String = s; } } } else switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() + Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() + Src->CastInt64(); break; default: *Dst = Dst->CastInt32() + Src->CastInt32(); break; } #endif break; } case IMinus: case IMinusEquals: { #if VM_DECOMP if (Log) Log->Print("%p Minus %s -= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() - Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() - Src->CastInt64(); break; default: *Dst = Dst->CastInt32() - Src->CastInt32(); break; } #endif break; } case IMul: case IMulEquals: { #if VM_DECOMP if (Log) Log->Print("%p Mul %s *= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() * Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() * Src->CastInt64(); break; default: *Dst = Dst->CastInt32() * Src->CastInt32(); break; } #endif break; } case IDiv: case IDivEquals: { #if VM_DECOMP if (Log) Log->Print("%p Div %s /= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() / Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() / Src->CastInt64(); break; default: *Dst = Dst->CastInt32() / Src->CastInt32(); break; } #endif break; } case IMod: { #if VM_DECOMP if (Log) Log->Print("%p Mod %s %%= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = fmod(Dst->CastDouble(), Src->CastDouble()); break; case GV_INT64: *Dst = Dst->CastInt64() % Src->CastInt64(); break; default: *Dst = Dst->CastInt32() % Src->CastInt32(); break; } #endif break; } case IPostInc: case IPreInc: { #if VM_DECOMP if (Log) Log->Print("%p PostInc %s\n", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif - GResolveRef(v) Resolve(); + LResolveRef(v) Resolve(); #ifdef VM_EXECUTE switch (v->Type) { case GV_DOUBLE: *v = v->Value.Dbl + 1; break; case GV_INT64: *v = v->Value.Int64 + 1; break; default: *v = v->CastInt32() + 1; break; } #endif break; } case IPostDec: case IPreDec: { #if VM_DECOMP if (Log) Log->Print("%p PostDec %sn", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif - GResolveRef(v) Resolve(); + LResolveRef(v) Resolve(); #ifdef VM_EXECUTE switch (v->Type) { case GV_DOUBLE: *v = v->Value.Dbl - 1; break; case GV_INT64: *v = v->Value.Int64 - 1; break; default: *v = v->CastInt32() - 1; break; } #endif break; } case IEquals: { #if VM_DECOMP if (Log) Log->Print("%p %s == %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) == 0; #endif break; } case INotEquals: { #if VM_DECOMP if (Log) Log->Print( "%p %s != %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) != 0; #endif break; } case ILessThan: { #if VM_DECOMP if (Log) Log->Print("%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) < 0; #endif break; } case ILessThanEqual: { #if VM_DECOMP if (Log) Log->Print( "%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) <= 0; #endif break; } case IGreaterThan: { #if VM_DECOMP if (Log) Log->Print("%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) > 0; #endif break; } case IGreaterThanEqual: { #if VM_DECOMP if (Log) Log->Print("%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) >= 0; #endif break; } case ICallMethod: { LFunc *Meth = *c.fn++; if (!Meth) { Log->Print( "%s ICallMethod error: No method struct.\n", Code->AddrToSourceRef(CurrentScriptAddress - sizeof(Meth))); SetScriptError; break; } #ifdef VM_DECOMP if (Log) { Log->Print("%p Call: %s = %s(", CurrentScriptAddress - sizeof(Meth) - 1, c.r[0].GetStr(), Meth->Method.Get()); } #endif - GResolveRef(Ret) Resolve(); + LResolveRef(Ret) Resolve(); uint16 Args = *c.u16++; #ifdef VM_EXECUTE LScriptArguments Arg(Vm, Ret); #endif for (int i=0; iPrint("%s%s", i?", ":"", c.r[0].GetStr()); #endif #if VM_EXECUTE Arg[i] = Resolve(); CheckParam(Arg[i] != NULL); #else c.r++; #endif } #if VM_DECOMP if (Log) Log->Print(")\n"); #endif #if VM_EXECUTE - LHostFunc *Hf = dynamic_cast(Meth); - if (Hf) - { - if (!(Hf->Context->*(Hf->Func))(Arg)) + LHostFunc *Hf = dynamic_cast(Meth); + if (Hf) { - if (Log) - Log->Print( "%s ICallMethod error: Method '%s' failed.\n", - Code->AddrToSourceRef(CurrentScriptAddress), - Meth->Method.Get()); - SetScriptError; - } - } - else - { - // Fixme - if (!Meth->Call(NULL, Arg)) + if (!(Hf->Context->*(Hf->Func))(Arg)) + { + if (Log) + Log->Print( "%s ICallMethod error: Method '%s' failed.\n", + Code->AddrToSourceRef(CurrentScriptAddress), + Meth->Method.Get()); + SetScriptError; + } + } + else { - if (Log) - Log->Print( "%s ICallMethod error: Method '%s' failed.\n", - Code->AddrToSourceRef(CurrentScriptAddress), - Meth->Method.Get()); - SetScriptError; + // Fixme + if (!Meth->Call(NULL, Arg)) + { + if (Log) + Log->Print( "%s ICallMethod error: Method '%s' failed.\n", + Code->AddrToSourceRef(CurrentScriptAddress), + Meth->Method.Get()); + SetScriptError; + } } - } - Arg.Length(0); // It doesn't own the variants, so don't delete them. + Arg.Length(0); // It doesn't own the variants, so don't delete them. #endif break; } case ICallScript: { int32 FuncAddr = *c.i32++; if (FuncAddr < 0 || (uint32_t)FuncAddr >= Code->ByteCode.Length()) { - Log->Print( "%s ICallScript error: Script function call invalid addr '%p'.\n", - Code->AddrToSourceRef(CurrentScriptAddress - sizeof(FuncAddr)), - FuncAddr); + if (Log) + Log->Print( "%s ICallScript error: Script function call invalid addr '%p'.\n", + Code->AddrToSourceRef(CurrentScriptAddress - sizeof(FuncAddr)), + FuncAddr); SetScriptError; break; } uint16 Frame = *c.u16++; #if VM_DECOMP if (Log) Log->Print("%p CallScript: %s = %p(frame=%i)(", CurrentScriptAddress - 5, c.r[0].GetStr(), FuncAddr, Frame); #endif #ifdef VM_EXECUTE // Set up stack for function call int CurFrameSize = Frames.Last().CurrentFrameSize; StackFrame &Sf = Frames.New(); Sf.CurrentFrameSize = Frame; Sf.PrevFrameStart = Locals.Length() ? Scope[1] - &Locals[0] : 0; Sf.ReturnValue = *c.r++; if (Sf.ReturnValue.Scope == SCOPE_LOCAL) Sf.ReturnValue.Index -= CurFrameSize; uint16 Args = *c.u16++; // Increase the local stack size size_t LocalsBase = Locals.Length(); size_t LocalsPos = Scope[SCOPE_LOCAL] - Locals.AddressOf(); Locals.SetFixedLength(false); Locals.Length(LocalsBase + Frame); Locals.SetFixedLength(); Scope[SCOPE_LOCAL] = Locals.AddressOf(LocalsPos); // Put the arguments of the function call into the local array LArray Arg; #else - GResolveRef(Ret) Resolve(); + LResolveRef(Ret) Resolve(); int Args = *c.u16++; #endif for (int i=0; iPrint("%s%s", i?",":"", c.r[0].GetStr()); #endif #if VM_EXECUTE Locals[LocalsBase+i] = *Resolve(); #else c.r++; #endif } #if VM_EXECUTE // Set IP to start of function Sf.ReturnIp = CurrentScriptAddress; c.u8 = Base + FuncAddr; Scope[SCOPE_LOCAL] = Locals.AddressOf(LocalsBase); // This can evaluation to NULL when there is NO locals. #endif #if VM_DECOMP if (Log) Log->Print(")\n"); #endif break; } case IRet: { #if VM_DECOMP if (Log) Log->Print("%p Ret %s\n", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif - GResolveRef(ReturnValue) Resolve(); + LResolveRef(ReturnValue) Resolve(); #ifdef VM_EXECUTE if (Frames.Length() > 0) { StackFrame Sf = Frames[Frames.Length()-1]; LVarRef &Ret = Sf.ReturnValue; LVariant *RetVar = &Scope[Ret.Scope][Ret.Index]; // LgiTrace("IRet to %i:%i\n", Ret.Scope, Ret.Index); if (Ret.Scope == SCOPE_LOCAL) LAssert(Locals.PtrCheck(RetVar)); *RetVar = *ReturnValue; CheckParam(RetVar->Type == ReturnValue->Type); Frames.Length(Frames.Length()-1); Locals.SetFixedLength(false); if (Locals.Length() >= Sf.CurrentFrameSize) { ssize_t Base = Locals.Length() - Sf.CurrentFrameSize; + /* if (ArgsOutput) { if (Frames.Length() == 0) { for (unsigned i=0; iLength(); i++) { *(*ArgsOutput)[i] = Locals[Base+i]; } } } + */ + // LgiTrace("%s:%i Locals %i -> %i\n", _FL, Locals.Length(), Base); Locals.Length(Base); Scope[SCOPE_LOCAL] = &Locals[Sf.PrevFrameStart]; } else { // LgiTrace("%s:%i - Locals %i -> %i\n", _FL, Locals.Length(), 0); Locals.Length(0); Scope[SCOPE_LOCAL] = NULL; } Locals.SetFixedLength(); c.u8 = Base + Sf.ReturnIp; } else { ExitScriptExecution; } #endif break; } case IArrayGet: { #if VM_DECOMP if (Log) Log->Print( "%p ArrayGet %s = %s[%s]\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Var) Resolve(); - GResolveRef(Idx) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Var) Resolve(); + LResolveRef(Idx) Resolve(); #ifdef VM_EXECUTE switch (Var->Type) { case GV_LIST: { CheckParam(Var->Value.Lst); LVariant *t = Var->Value.Lst->ItemAt(Idx->CastInt32()); if (t) { if (Var == Dst) { if (Var->Value.Lst->Delete(t)) { *Var = *t; DeleteObj(t); } else CheckParam(!"List delete failed."); } else *Dst = *t; } else Dst->Empty(); break; } case GV_HASHTABLE: { CheckParam(Var->Value.Hash); LVariant *t = (LVariant*)Var->Value.Hash->Find(Idx->CastString()); if (t) *Dst = *t; else Dst->Empty(); break; } case GV_CUSTOM: { LCustomType *T = Var->Value.Custom.Dom; size_t Sz = T->Sizeof(); int Index = Idx->CastInt32(); Dst->Type = GV_CUSTOM; Dst->Value.Custom.Dom = T; Dst->Value.Custom.Data = Var->Value.Custom.Data + (Sz * Index); break; } case GV_STRING: { auto c = Var->Str(); auto i = Idx->CastInt64(); if (!c || i < 0) break; LUtf8Ptr p(c); uint32_t ch; do { ch = p; if (i-- == 0) { *Dst = ch; break; } p++; } while (ch); break; } default: { LString s; s.Printf("%s IArrayGet warning: Can't array deref variant type %i\n", Code->AddrToSourceRef(CurrentScriptAddress), Var->Type); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IArraySet: { #if VM_DECOMP if (Log) Log->Print( "%p ArraySet %s[%s] = %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr()); #endif - GResolveRef(Var) Resolve(); - GResolveRef(Idx) Resolve(); - GResolveRef(Val) Resolve(); + LResolveRef(Var) Resolve(); + LResolveRef(Idx) Resolve(); + LResolveRef(Val) Resolve(); #ifdef VM_EXECUTE switch (Var->Type) { case GV_LIST: { CheckParam(Var->Value.Lst); (*Var->Value.Lst).Insert(new LVariant(*Val), Idx->CastInt32()); break; } case GV_HASHTABLE: { CheckParam(Var->Value.Hash); LVariant *Old = (LVariant*)Var->Value.Hash->Find(Idx->CastString()); DeleteObj(Old); Var->Value.Hash->Add(Idx->CastString(), new LVariant(*Val)); break; } default: { LString s; s.Printf("%s IArraySet warning: Can't dereference type '%s'\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Var->Type)); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IAnd: { #if VM_DECOMP if (Log) Log->Print("%p %s && %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = (Dst->CastInt32() != 0) && (Src->CastInt32() != 0); #endif break; } case IOr: { #if VM_DECOMP if (Log) Log->Print("%p %s || %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Src) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = (Dst->CastInt32() != 0) || (Src->CastInt32() != 0); #endif break; } case INot: { #if VM_DECOMP if (Log) Log->Print("%p %s = !%s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[0].GetStr()); #endif - GResolveRef(Dst) Resolve(); + LResolveRef(Dst) Resolve(); #ifdef VM_EXECUTE *Dst = !Dst->CastBool(); #endif break; } case IDomGet: { #if VM_DECOMP if (Log) Log->Print("%p %s = %s->DomGet(%s, %s)\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr(), c.r[3].GetStr()); #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Dom) Resolve(); - GResolveRef(Name) Resolve(); - GResolveRef(Arr) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Dom) Resolve(); + LResolveRef(Name) Resolve(); + LResolveRef(Arr) Resolve(); #ifdef VM_EXECUTE // Return "NULL" in Dst on error if (Dst != Dom) Dst->Empty(); switch (Dom->Type) { case GV_DOM: case GV_STREAM: case GV_LSURFACE: { auto *dom = Dom->CastDom(); CheckParam(dom != NULL); char *sName = Name->Str(); CheckParam(sName); bool Ret = dom->GetVariant(sName, *Dst, CastArrayIndex(Arr)); if (!Ret) { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; } break; } case GV_DATETIME: { CheckParam(Dom->Value.Date != NULL); char *sName = Name->Str(); CheckParam(sName); bool Ret = Dom->Value.Date->GetVariant(sName, *Dst, CastArrayIndex(Arr)); if (!Ret) { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; } break; } case GV_CUSTOM: { LCustomType *Type = Dom->Value.Custom.Dom; if (Type) { int Fld; if (Name->Type == GV_INT32) Fld = Name->Value.Int; else Fld = Type->IndexOf(Name->Str()); int Index = Arr ? Arr->CastInt32() : 0; Type->Get(Fld, *Dst, Dom->Value.Custom.Data, Index); } break; } case GV_LIST: { CheckParam(Dom->Value.Lst); char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); if (p == ObjLength) (*Dst) = (int)Dom->Value.Lst->Length(); break; } case GV_HASHTABLE: { CheckParam(Dom->Value.Hash); char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); if (p == ObjLength) (*Dst) = (int)Dom->Value.Hash->Length(); break; } case GV_BINARY: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); if (p == ObjLength) (*Dst) = Dom->Value.Binary.Length; break; } case GV_INT32: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); switch (p) { case TypeString: { char s[32]; sprintf_s(s, sizeof(s), "%i", Dom->Value.Int); *Dst = s; break; } case TypeDouble: { *Dst = (double)Dom->Value.Int; break; } default: { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected int32 member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } break; } case GV_INT64: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); switch (p) { case TypeString: { char s[32]; sprintf_s(s, sizeof(s), LPrintfInt64, Dom->Value.Int64); *Dst = s; break; } case TypeDouble: { *Dst = (double)Dom->Value.Int64; break; } default: { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected int64 member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } break; } case GV_DOUBLE: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); switch (p) { case TypeString: { char s[32]; sprintf_s(s, sizeof(s), "%g", Dom->Value.Dbl); *Dst = s; break; } case TypeInt: { *Dst = (int64)Dom->Value.Dbl; break; } default: { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected double member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } break; } case GV_STRING: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); switch (p) { case ObjLength: { (*Dst) = (int)strlen(Dom->Str()); break; } case TypeInt: { (*Dst) = Dom->CastInt32(); break; } case TypeDouble: { (*Dst) = Dom->CastDouble(); break; } default: { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected string member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } break; } case GV_NULL: { LString s; s.Printf("%s IDomGet warning: Can't deref NULL object.\n", Code->AddrToSourceRef(CurrentScriptAddress)); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } default: { LString s; s.Printf("%s IDomGet warning: Unexpected type %s (Src=%s:%i IP=0x%x).\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), _FL, CurrentScriptAddress); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IDomSet: { #if VM_DECOMP if (Log) Log->Print("%p %s->DomSet(%s, %s) = %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr(), c.r[3].GetStr()); #endif - GResolveRef(Dom) Resolve(); - GResolveRef(Name) Resolve(); - GResolveRef(Arr) Resolve(); - GResolveRef(Value) Resolve(); + LResolveRef(Dom) Resolve(); + LResolveRef(Name) Resolve(); + LResolveRef(Arr) Resolve(); + LResolveRef(Value) Resolve(); #ifdef VM_EXECUTE char *sName = Name->Str(); if (!sName) { if (Log) Log->Print("%s IDomSet error: No name string.\n", Code->AddrToSourceRef(CurrentScriptAddress)); SetScriptError; break; } switch (Dom->Type) { case GV_DOM: // case GV_GFILE: case GV_STREAM: case GV_LSURFACE: { auto *dom = Dom->CastDom(); CheckParam(dom != NULL); bool Ret = dom->SetVariant(sName, *Value, CastArrayIndex(Arr)); if (!Ret) { if (Log) Log->Print("%s IDomSet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); Status = ScriptWarning; } break; } case GV_DATETIME: { CheckParam(Dom->Value.Date != NULL); bool Ret = Dom->Value.Date->SetVariant(sName, *Value, CastArrayIndex(Arr)); if (!Ret) { if (Log) Log->Print("%s IDomSet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); Status = ScriptWarning; } break; } case GV_CUSTOM: { LCustomType *Type = Dom->Value.Custom.Dom; if (Type) { int Fld; if (IsDigit(*sName)) Fld = atoi(sName); else Fld = Type->IndexOf(sName); int Index = Arr ? Arr->CastInt32() : 0; if (!Type->Set(Fld, *Value, Dom->Value.Custom.Data, Index) && Log) { Log->Print("%s IDomSet warning: Couldn't set '%s' on custom type.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); } } break; } case GV_STRING: { LDomProperty p = LStringToDomProp(sName); switch (p) { case ObjLength: { char *s; int DLen = Value->CastInt32(); if (DLen && (s = new char[DLen+1])) { size_t SLen = Dom->Str() ? strlen(Dom->Str()) : 0; if (SLen) memcpy(s, Dom->Str(), SLen); memset(s+SLen, ' ', DLen-SLen); s[DLen] = 0; DeleteArray(Dom->Value.String); Dom->Value.String = s; } else Dom->Empty(); break; } case TypeInt: { *Dom = Value->CastInt32(); Dom->Str(); break; } case TypeDouble: { *Dom = Value->CastDouble(); Dom->Str(); break; } default: { if (Log) Log->Print("%s IDomSet warning: Unexpected string member %s.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; break; } } break; } default: { if (Log) Log->Print("%s IDomSet warning: Unexpected type %s.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type)); Status = ScriptWarning; break; } } #endif break; } case IDomCall: { #if VM_DECOMP if (Log) Log->Print("%p %s = %s->DomCall(%s, ", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr()); #else LVarRef DstRef = *c.r; #endif - GResolveRef(Dst) Resolve(); - GResolveRef(Dom) Resolve(); - GResolveRef(Name) Resolve(); + LResolveRef(Dst) Resolve(); + LResolveRef(Dom) Resolve(); + LResolveRef(Name) Resolve(); #ifdef VM_EXECUTE - GResolveRef(Args) Resolve(); - int ArgCount = Args->CastInt32(); - char *sName = Name->Str(); - CheckParam(sName) + LResolveRef(Args) Resolve(); + int ArgCount = Args->CastInt32(); + char *sName = Name->Str(); + CheckParam(sName) - if (Dom->Type == GV_CUSTOM) - { - #define DEBUG_CUSTOM_METHOD_CALL 1 - - LCustomType *t = Dom->Value.Custom.Dom; - CheckParam(t); - LCustomType::Method *m = t->GetMethod(sName); - CheckParam(m); - CheckParam(m->Params.Length() == ArgCount); + if (Dom->Type == GV_CUSTOM) + { + #define DEBUG_CUSTOM_METHOD_CALL 1 - // Set up new stack frame... - StackFrame &Sf = Frames.New(); - Sf.CurrentFrameSize = m->FrameSize; - Sf.PrevFrameStart = Locals.Length() ? Scope[1] - &Locals[0] : 0; - Sf.ReturnValue = DstRef; - - // Increase the local stack size - AddLocalSize(m->FrameSize + 1); + auto t = Dom->Value.Custom.Dom; + CheckParam(t); + auto m = t->GetMethod(sName); + CheckParam(m); + CheckParam(m->Params.Length() == ArgCount); - #if DEBUG_CUSTOM_METHOD_CALL - LgiTrace("CustomType.Call(%s) Args=%i, Frame=%i, Addr=%i, LocalsBase=%i ", - sName, ArgCount, m->FrameSize, m->Address, LocalsBase); - #endif + // Set up new stack frame... + StackFrame &Sf = Frames.New(); + Sf.CurrentFrameSize = m->FrameSize; + Sf.PrevFrameStart = Locals.Length() ? Scope[1] - &Locals[0] : 0; + Sf.ReturnValue = DstRef; + + // Increase the local stack size + AddLocalSize(m->FrameSize + 1); - size_t i = LocalsBase; - Locals[i++] = *Dom; // this pointer... - #if DEBUG_CUSTOM_METHOD_CALL - LString s = Locals[i-1].ToString(); - LgiTrace("This=%s, ", s.Get()); - #endif - size_t end = i + ArgCount; - while (i < end) - { - Locals[i++] = *Resolve(); #if DEBUG_CUSTOM_METHOD_CALL - s = Locals[i-1].ToString(); - LgiTrace("[%i]=%s, ", i-1, s.Get()); + LgiTrace("CustomType.Call(%s) Args=%i, Frame=%i, Addr=%i, LocalsBase=%i ", + sName, ArgCount, m->FrameSize, m->Address, LocalsBase); #endif - } - - // Now adjust the local stack to point to the locals for the function - Scope[1] = Locals.Length() ? &Locals[LocalsBase] : NULL; - - // Set IP to start of function - Sf.ReturnIp = CurrentScriptAddress; - c.u8 = Base + m->Address; - - #if DEBUG_CUSTOM_METHOD_CALL - LgiTrace("\n"); - #endif - break; - } - - LScriptArguments Arg(Vm, Dst); - Arg.Length(ArgCount); - for (int i=0; iType); - } - else switch (Dom->Type) - { - case GV_DOM: - case GV_STREAM: - case GV_LSURFACE: - { - auto *dom = Dom->CastDom(); - CheckParam(dom); - bool Ret = dom->CallMethod(sName, Arg); - if (!Ret) + + size_t i = LocalsBase; + Locals[i++] = *Dom; // this pointer... + #if DEBUG_CUSTOM_METHOD_CALL + LString s = Locals[i-1].ToString(); + LgiTrace("This=%s, ", s.Get()); + #endif + size_t end = i + ArgCount; + while (i < end) { - Dst->Empty(); - if (Log) - Log->Print("%s IDomCall warning: %s(...) failed.\n", - Code->AddrToSourceRef(CurrentScriptAddress), - sName); - Status = ScriptWarning; + Locals[i++] = *Resolve(); + #if DEBUG_CUSTOM_METHOD_CALL + s = Locals[i-1].ToString(); + LgiTrace("[%i]=%s, ", i-1, s.Get()); + #endif } + + // Now adjust the local stack to point to the locals for the function + Scope[1] = Locals.Length() ? &Locals[LocalsBase] : NULL; + + // Set IP to start of function + Sf.ReturnIp = CurrentScriptAddress; + c.u8 = Base + m->Address; + + #if DEBUG_CUSTOM_METHOD_CALL + LgiTrace("\n"); + #endif break; - } - case GV_DATETIME: - { - CheckParam(Dom->Value.Date); - bool Ret = Dom->Value.Date->CallMethod(sName, Dst, Arg); - if (!Ret) - { - Dst->Empty(); - if (Log) - Log->Print("%s IDomCall warning: %s(...) failed.\n", - Code->AddrToSourceRef(CurrentScriptAddress), - sName); - Status = ScriptWarning; - } - break; - } - case GV_LIST: - { - CheckParam(Dom->Value.Lst); - switch (p) - { - case ObjLength: - { - *Dst = (int64)Dom->Value.Lst->Length(); - break; - } - case ContainerAdd: - { - if (Arg.Length() > 0 && - Arg[0]) - { - int Index = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; + } - LVariant *v = new LVariant; - *v = *Arg[0]; - Dom->Value.Lst->Insert(v, Index); - } - break; - } - case ContainerDelete: - { - for (unsigned i=0; iCastInt32(); - LVariant *Elem = Dom->Value.Lst->ItemAt(n); - if (Elem) - { - Dom->Value.Lst->Delete(Elem); - DeleteObj(Elem); - } - } - } - break; - } - case ContainerHasKey: - { - if (Arg.Length() > 0 && Arg[0]) - { - int Index = Arg[0]->CastInt32(); - *Dst = (bool) (Index >= 0 && Index < (int)Dom->Value.Lst->Length()); - } - else - { - *Dst = false; - } - break; - } - case ContainerSort: - { - LVariant *Param = Arg.Length() > 0 ? Arg[0] : NULL; - Dom->Value.Lst->Sort(LVariantCmp, (NativeInt)Param); - break; - } - default: + LScriptArguments Arg(Vm, Dst); + Arg.Length(ArgCount); + for (int i=0; iType); + } + else switch (Dom->Type) + { + case GV_DOM: + case GV_STREAM: + case GV_LSURFACE: + { + auto *dom = Dom->CastDom(); + CheckParam(dom); + bool Ret = dom->CallMethod(sName, Arg); + if (!Ret) { Dst->Empty(); if (Log) - Log->Print( "%s IDomCall warning: Unexpected list member '%s'.\n", + Log->Print("%s IDomCall warning: %s(...) failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; - break; } + break; } - break; - } - case GV_HASHTABLE: - { - CheckParam(Dom->Value.Hash); - switch (p) + case GV_DATETIME: { - case ObjLength: - { - *Dst = Dom->Value.Hash->Length(); - break; - } - case ContainerAdd: - { - if (Arg.Length() == 2 && - Arg[0] && - Arg[1]) - { - char *Key = Arg[1]->Str(); - if (Key) - { - LVariant *v = new LVariant; - *v = *Arg[0]; - Dom->Value.Hash->Add(Key, v); - } - } - break; - } - case ContainerDelete: - { - if (Arg.Length() == 1 && - Arg[0]) - { - char *Key = Arg[0]->Str(); - if (Key) - { - LVariant *v = (LVariant*) Dom->Value.Hash->Find(Key); - if (v) - { - Dom->Value.Hash->Delete(Key); - delete v; - } - } - } - break; - } - case ContainerHasKey: - { - if (Arg.Length() > 0 && Arg[0]) - { - char *Key = Arg[0]->Str(); - *Dst = (bool) (Dom->Value.Hash->Find(Key) != NULL); - } - else - { - *Dst = false; - } - break; - } - default: + CheckParam(Dom->Value.Date); + bool Ret = Dom->Value.Date->CallMethod(sName, Dst, Arg); + if (!Ret) { Dst->Empty(); if (Log) - Log->Print("%s IDomCall warning: Unexpected hashtable member '%s'.\n", + Log->Print("%s IDomCall warning: %s(...) failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; - break; } - } - break; - } - case GV_BINARY: - { - switch (p) - { - default: - break; - case ObjLength: - *Dst = Dom->Value.Binary.Length; - break; - } - break; - } - case GV_STRING: - { - if (Arg.Length() > 0 && !Arg[0]) - { - Dst->Empty(); break; } - - switch (p) + case GV_LIST: { - case ObjLength: + CheckParam(Dom->Value.Lst); + switch (p) { - char *s = Dom->Str(); - *Dst = (int) (s ? strlen(s) : 0); - break; - } - case StrJoin: - { - switch (Arg[0]->Type) + case ObjLength: + { + *Dst = (int64)Dom->Value.Lst->Length(); + break; + } + case ContainerAdd: { - case GV_LIST: + if (Arg.Length() > 0 && + Arg[0]) { - LStringPipe p(256); - List *Lst = Arg[0]->Value.Lst; - const char *Sep = Dom->CastString(); - auto It = Lst->begin(); - LVariant *v = *It; - if (v) + int Index = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; + + LVariant *v = new LVariant; + *v = *Arg[0]; + Dom->Value.Lst->Insert(v, Index); + } + break; + } + case ContainerDelete: + { + for (unsigned i=0; iCastInt32(); + LVariant *Elem = Dom->Value.Lst->ItemAt(n); + if (Elem) { - Tmp = *v; - p.Print("%s%s", Sep, Tmp.CastString()); + Dom->Value.Lst->Delete(Elem); + DeleteObj(Elem); } } - Dst->OwnStr(p.NewStr()); - break; } - default: + break; + } + case ContainerHasKey: + { + if (Arg.Length() > 0 && Arg[0]) + { + int Index = Arg[0]->CastInt32(); + *Dst = (bool) (Index >= 0 && Index < (int)Dom->Value.Lst->Length()); + } + else { - *Dst = *Arg[0]; - Dst->CastString(); - break; + *Dst = false; } + break; } - break; - } - case StrSplit: - { - const char *Sep = Arg[0]->Str(); - if (!Sep) + case ContainerSort: + { + LVariant *Param = Arg.Length() > 0 ? Arg[0] : NULL; + Dom->Value.Lst->Sort(LVariantCmp, (NativeInt)Param); + break; + } + default: { Dst->Empty(); + if (Log) + Log->Print( "%s IDomCall warning: Unexpected list member '%s'.\n", + Code->AddrToSourceRef(CurrentScriptAddress), + sName); + Status = ScriptWarning; + break; + } + } + break; + } + case GV_HASHTABLE: + { + CheckParam(Dom->Value.Hash); + switch (p) + { + case ObjLength: + { + *Dst = Dom->Value.Hash->Length(); + break; + } + case ContainerAdd: + { + if (Arg.Length() == 2 && + Arg[0] && + Arg[1]) + { + char *Key = Arg[1]->Str(); + if (Key) + { + LVariant *v = new LVariant; + *v = *Arg[0]; + Dom->Value.Hash->Add(Key, v); + } + } break; } - - LVariant Tmp; - if (Dst == Dom) + case ContainerDelete: { - Tmp = *Dom; - Dom = &Tmp; + if (Arg.Length() == 1 && + Arg[0]) + { + char *Key = Arg[0]->Str(); + if (Key) + { + LVariant *v = (LVariant*) Dom->Value.Hash->Find(Key); + if (v) + { + Dom->Value.Hash->Delete(Key); + delete v; + } + } + } + break; } - - Dst->SetList(); - - size_t SepLen = strlen(Sep); - int MaxSplit = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; - const char *c = Dom->CastString(); - while (c && *c) + case ContainerHasKey: { - if (MaxSplit > 0 && (int)Dst->Value.Lst->Length() >= MaxSplit) - break; - - const char *next = strstr(c, Sep); - if (!next) - break; - - LVariant *v = new LVariant; - v->OwnStr(NewStr(c, next - c)); - Dst->Value.Lst->Insert(v); - - c = next + SepLen; + if (Arg.Length() > 0 && Arg[0]) + { + char *Key = Arg[0]->Str(); + *Dst = (bool) (Dom->Value.Hash->Find(Key) != NULL); + } + else + { + *Dst = false; + } + break; + } + default: + { + Dst->Empty(); + if (Log) + Log->Print("%s IDomCall warning: Unexpected hashtable member '%s'.\n", + Code->AddrToSourceRef(CurrentScriptAddress), + sName); + Status = ScriptWarning; + break; } - - if (c && *c) - { - LVariant *v = new LVariant; - v->OwnStr(NewStr(c)); - Dst->Value.Lst->Insert(v); - } + } + break; + } + case GV_BINARY: + { + switch (p) + { + default: + break; + case ObjLength: + *Dst = Dom->Value.Binary.Length; + break; + } + break; + } + case GV_STRING: + { + if (Arg.Length() > 0 && !Arg[0]) + { + Dst->Empty(); break; } - case StrSplitDelimit: + + switch (p) { - const char *Sep = Arg[0]->Str(); - if (!Sep) + case ObjLength: { - Dst->Empty(); + char *s = Dom->Str(); + *Dst = (int) (s ? strlen(s) : 0); break; } - - LVariant Tmp; - if (Dst == Dom) - { - Tmp = *Dom; - Dom = &Tmp; - } - - Dst->SetList(); - - int MaxSplit = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; - const char *c = Dom->CastString(); - while (c && *c) + case StrJoin: { - if (MaxSplit > 0 && (int)Dst->Value.Lst->Length() >= MaxSplit) - break; - - const char *next = c; - while (*next && !strchr(Sep, *next)) - next++; - - LVariant *v = new LVariant; - v->OwnStr(NewStr(c, next - c)); - Dst->Add(v); - - for (c = next; *c && strchr(Sep, *c); c++) - ; - } - - if (c && *c) - Dst->Add(new LVariant(c)); - break; - } - case StrFind: - { - const char *s = Dom->Str(); - if (!s) - { - *Dst = -1; + switch (Arg[0]->Type) + { + case GV_LIST: + { + LStringPipe p(256); + List *Lst = Arg[0]->Value.Lst; + const char *Sep = Dom->CastString(); + auto It = Lst->begin(); + LVariant *v = *It; + if (v) + { + LVariant Tmp = *v; + p.Print("%s", Tmp.CastString()); + while ((v = *(++It))) + { + Tmp = *v; + p.Print("%s%s", Sep, Tmp.CastString()); + } + } + Dst->OwnStr(p.NewStr()); + break; + } + default: + { + *Dst = *Arg[0]; + Dst->CastString(); + break; + } + } break; } + case StrSplit: + { + const char *Sep = Arg[0]->Str(); + if (!Sep) + { + Dst->Empty(); + break; + } + + LVariant Tmp; + if (Dst == Dom) + { + Tmp = *Dom; + Dom = &Tmp; + } - ssize_t sLen = Strlen(s); - auto sub = Arg[0]->Str(); - auto start = Arg.Length() > 1 ? Arg[1]->CastInt32() : 0; - auto end = Arg.Length() > 2 ? Arg[2]->CastInt32() : -1; + Dst->SetList(); + + size_t SepLen = strlen(Sep); + int MaxSplit = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; + const char *c = Dom->CastString(); + while (c && *c) + { + if (MaxSplit > 0 && (int)Dst->Value.Lst->Length() >= MaxSplit) + break; - if (start >= sLen) - { - *Dst = -1; + const char *next = strstr(c, Sep); + if (!next) + break; + + LVariant *v = new LVariant; + v->OwnStr(NewStr(c, next - c)); + Dst->Value.Lst->Insert(v); + + c = next + SepLen; + } + + if (c && *c) + { + LVariant *v = new LVariant; + v->OwnStr(NewStr(c)); + Dst->Value.Lst->Insert(v); + } break; } - char *sStart = (char*)s + start; - char *pos; - if (end >= 0) - pos = Strnstr(sStart, sub, end); - else - pos = Strstr(sStart, sub); + case StrSplitDelimit: + { + const char *Sep = Arg[0]->Str(); + if (!Sep) + { + Dst->Empty(); + break; + } + + LVariant Tmp; + if (Dst == Dom) + { + Tmp = *Dom; + Dom = &Tmp; + } + + Dst->SetList(); + + int MaxSplit = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; + const char *c = Dom->CastString(); + while (c && *c) + { + if (MaxSplit > 0 && (int)Dst->Value.Lst->Length() >= MaxSplit) + break; - if (pos) - *Dst = (int64) (pos - s); - else - *Dst = -1; - break; - } - case StrRfind: - { - const char *s = Dom->Str(); - if (!s) + const char *next = c; + while (*next && !strchr(Sep, *next)) + next++; + + LVariant *v = new LVariant; + v->OwnStr(NewStr(c, next - c)); + Dst->Add(v); + + for (c = next; *c && strchr(Sep, *c); c++) + ; + } + + if (c && *c) + Dst->Add(new LVariant(c)); + break; + } + case StrFind: { - *Dst = -1; - break; - } + const char *s = Dom->Str(); + if (!s) + { + *Dst = -1; + break; + } + + ssize_t sLen = Strlen(s); + auto sub = Arg[0]->Str(); + auto start = Arg.Length() > 1 ? Arg[1]->CastInt32() : 0; + auto end = Arg.Length() > 2 ? Arg[2]->CastInt32() : -1; - ssize_t sLen = strlen(s); - auto sub = Arg[0]->Str(); - auto start_idx = Arg.Length() > 1 ? Arg[1]->CastInt32() : 0; - auto end_idx = Arg.Length() > 2 ? Arg[2]->CastInt32() : -1; + if (start >= sLen) + { + *Dst = -1; + break; + } + char *sStart = (char*)s + start; + char *pos; + if (end >= 0) + pos = Strnstr(sStart, sub, end); + else + pos = Strstr(sStart, sub); - if (start_idx >= sLen) - { - *Dst = -1; + if (pos) + *Dst = (int64) (pos - s); + else + *Dst = -1; break; } - auto sublen = Strlen(sub); - auto cur = s + start_idx; - auto end = end_idx >= 0 ? cur + end_idx : NULL; - const char *pos = NULL; - while (true) + case StrRfind: { - cur = (end) - ? - Strnstr(cur, sub, end - cur) - : - Strstr(cur, sub); - if (cur) + const char *s = Dom->Str(); + if (!s) + { + *Dst = -1; + break; + } + + ssize_t sLen = strlen(s); + auto sub = Arg[0]->Str(); + auto start_idx = Arg.Length() > 1 ? Arg[1]->CastInt32() : 0; + auto end_idx = Arg.Length() > 2 ? Arg[2]->CastInt32() : -1; + + if (start_idx >= sLen) { - pos = cur; - cur += sublen; + *Dst = -1; + break; } - else break; - } - - if (pos) - *Dst = (int64) (pos - s); - else - *Dst = -1; - break; - } - case StrLower: - { - if (Dst != Dom) - *Dst = Dom->CastString(); + auto sublen = Strlen(sub); + auto cur = s + start_idx; + auto end = end_idx >= 0 ? cur + end_idx : NULL; + const char *pos = NULL; + while (true) + { + cur = (end) + ? + Strnstr(cur, sub, end - cur) + : + Strstr(cur, sub); + if (cur) + { + pos = cur; + cur += sublen; + } + else break; + } - StrLwr(Dst->Str()); - break; - } - case StrUpper: - { - if (Dst != Dom) - *Dst = Dom->CastString(); + if (pos) + *Dst = (int64) (pos - s); + else + *Dst = -1; + break; + } + case StrLower: + { + if (Dst != Dom) + *Dst = Dom->CastString(); + + StrLwr(Dst->Str()); + break; + } + case StrUpper: + { + if (Dst != Dom) + *Dst = Dom->CastString(); - StrUpr(Dst->Str()); - break; - } - case StrStrip: - { - auto s = Dom->Str(); - if (s) - { - const char *Delimit = Arg.Length() > 0 ? Arg[0]->Str() : NULL; - if (!Delimit) - Delimit = WhiteSpace; - - auto start = s; - auto end = s + Strlen(s); - while (start < end && strchr(Delimit, *start)) - start++; - - while (end > start && strchr(Delimit, end[-1])) - end--; - - Dst->OwnStr(NewStr(start, end - start)); + StrUpr(Dst->Str()); + break; } - else Dst->Empty(); - break; - } - case StrSub: - { - auto s = Dom->Str(); - if (s) + case StrStrip: + { + auto s = Dom->Str(); + if (s) + { + const char *Delimit = Arg.Length() > 0 ? Arg[0]->Str() : NULL; + if (!Delimit) + Delimit = WhiteSpace; + + auto start = s; + auto end = s + Strlen(s); + while (start < end && strchr(Delimit, *start)) + start++; + + while (end > start && strchr(Delimit, end[-1])) + end--; + + Dst->OwnStr(NewStr(start, end - start)); + } + else Dst->Empty(); + break; + } + case StrSub: { - ssize_t Start = Arg.Length() > 0 ? Arg[0]->CastInt32() : 0; - ssize_t End = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; - ssize_t Len = strlen(s); - if (End < 0 || End > Len) - End = Len; - if (Start < 0) - Start = 0; - if (Start <= End) - Dst->OwnStr(NewStr(s + Start, End - Start)); - else - Dst->Empty(); + auto s = Dom->Str(); + if (s) + { + ssize_t Start = Arg.Length() > 0 ? Arg[0]->CastInt32() : 0; + ssize_t End = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; + ssize_t Len = strlen(s); + if (End < 0 || End > Len) + End = Len; + if (Start < 0) + Start = 0; + if (Start <= End) + Dst->OwnStr(NewStr(s + Start, End - Start)); + else + Dst->Empty(); + } + else Dst->Empty(); + break; } - else Dst->Empty(); - break; + default: + { + Dst->Empty(); + if (Log) + Log->Print("%p IDomCall warning: Unexpected string member %s (%s:%i).\n", + CurrentScriptAddress, + sName, + _FL); + Status = ScriptWarning; + break; + } } - default: + break; + } + default: + { + const char *Type = LVariant::TypeToString(Dom->Type); + char t[32]; + if (!Type) { - Dst->Empty(); - if (Log) - Log->Print("%p IDomCall warning: Unexpected string member %s (%s:%i).\n", - CurrentScriptAddress, - sName, - _FL); - Status = ScriptWarning; - break; + sprintf_s(t, sizeof(t), "UnknownType(%i)", Dom->Type); + Type = t; } + + Dst->Empty(); + if (Log) + { + Log->Print("%s IDomCall warning: Unexpected type %s (Src=%s:%i IP=0x%x).\n", + Code->AddrToSourceRef(CurrentScriptAddress), + Type, + _FL, + CurrentScriptAddress); + } + Status = ScriptWarning; + break; } - break; } - default: - { - const char *Type = LVariant::TypeToString(Dom->Type); - char t[32]; - if (!Type) - { - sprintf_s(t, sizeof(t), "UnknownType(%i)", Dom->Type); - Type = t; - } - - Dst->Empty(); - if (Log) - { - Log->Print("%s IDomCall warning: Unexpected type %s (Src=%s:%i IP=0x%x).\n", - Code->AddrToSourceRef(CurrentScriptAddress), - Type, - _FL, - CurrentScriptAddress); - } - Status = ScriptWarning; - break; - } - } - Arg.Length(0); // It doesn't own the variants, so don't delete them. + Arg.Length(0); // It doesn't own the variants, so don't delete them. #else - LVariant *Count = NULL; - switch (c.r->Scope) - { - case SCOPE_GLOBAL: - Count = &Code->Globals[c.r->Index]; - c.r++; - break; - default: - OnException(_FL, CurrentScriptAddress, "Unsupported scope."); - return ScriptError; - } + LVariant *Count = NULL; + switch (c.r->Scope) + { + case SCOPE_GLOBAL: + Count = &Code->Globals[c.r->Index]; + c.r++; + break; + default: + OnException(_FL, CurrentScriptAddress, "Unsupported scope."); + return ScriptError; + } - int Args = Count->CastInt32(); - for (int i=0; iCastInt32(); + for (int i=0; iPrint("%s%s", i ? ", " : "", c.r->GetStr()); + #endif + c.r++; + } #if VM_DECOMP - if (Log) - Log->Print("%s%s", i ? ", " : "", c.r->GetStr()); + if (Log) + Log->Print(")\n"); #endif - c.r++; - } - #if VM_DECOMP - if (Log) - Log->Print(")\n"); - #endif #endif break; } case IBreakPoint: { #if VM_DECOMP if (Log) Log->Print("%p Debugger\n", CurrentScriptAddress-1); #elif VM_EXECUTE OnException(_FL, CurrentScriptAddress-1, "ShowDebugger"); return ScriptWarning; #endif break; } case IDebug: { #if VM_DECOMP if (Log) Log->Print("%p Debugger\n", CurrentScriptAddress-1); #elif VM_EXECUTE #ifdef WINDOWS __debugbreak(); #elif defined MAC __builtin_trap(); #elif defined LINUX Gtk::raise(SIGINT); #else #warning "Not impl." #endif #endif break; } #undef Resolve -#undef GResolveRef \ No newline at end of file +#undef LResolveRef \ No newline at end of file diff --git a/src/common/Coding/ScriptVM.cpp b/src/common/Coding/ScriptVM.cpp --- a/src/common/Coding/ScriptVM.cpp +++ b/src/common/Coding/ScriptVM.cpp @@ -1,2200 +1,2168 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/Box.h" #include "lgi/common/TabView.h" #include "lgi/common/TextLog.h" #include "lgi/common/List.h" #include "lgi/common/ToolBar.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Matrix.h" #include "lgi/common/Menu.h" #include "ScriptingPriv.h" #define TIME_INSTRUCTIONS 0 #define POST_EXECUTE_STATE 0 // #define BREAK_POINT 0x0000009F #define ExitScriptExecution c.u8 = e #define SetScriptError c.u8 = e; Status = ScriptError #define CurrentScriptAddress (c.u8 - Base) #define CheckParam(ptr) if (!(ptr)) \ { \ OnException(_FL, CurrentScriptAddress-1, #ptr); \ c.u8 = e; \ Status = ScriptError; \ break; \ } #define AddLocalSize(NewSize) \ size_t LocalsBase = Locals.Length(); \ Locals.SetFixedLength(false); \ /* LgiTrace("%s:%i - Locals %i -> %i\n", _FL, LocalsBase, LocalsBase + NewSize); */ \ Locals.Length(LocalsBase + NewSize); \ Scope[SCOPE_LOCAL] = &Locals[LocalsBase]; \ Locals.SetFixedLength(); #ifdef WIN32 extern "C" uint64 __cdecl CallExtern64(void *FuncAddr, NativeInt *Ret, uint32_t Args, void *Arg); #elif defined(LINUX) #include #endif int LVariantCmp(LVariant *a, LVariant *b, NativeInt Data) { LVariant *Param = (LVariant*) Data; if (!a || !b) return 0; if (a->Type == GV_STRING && b->Type == GV_STRING) { const char *Empty = ""; const char *as = a->Str(); const char *bs = b->Str(); return _stricmp(as?as:Empty, bs?bs:Empty); } else if (a->Type == GV_DOM && b->Type == GV_DOM && Param) { const char *Fld = Param->Str(); int Dir = 1; if (Fld && *Fld == '-') { Fld++; Dir = -1; } LVariant av, bv; if (a->Value.Dom->GetValue(Fld, av) && b->Value.Dom->GetValue(Fld, bv)) { return LVariantCmp(&av, &bv, 0) * Dir; } } else if (a->Type == GV_INT32 && b->Type == GV_INT32) { return a->CastInt32() - b->CastInt32(); } else if (a->Type == GV_DATETIME && b->Type == GV_DATETIME) { return a->Value.Date->Compare(b->Value.Date); } else { LAssert(!"Impl a handler for this type."); } return 0; } inline LVariantType DecidePrecision(LVariantType a, LVariantType b) { if (a == GV_DOUBLE || b == GV_DOUBLE) return GV_DOUBLE; if (a == GV_INT64 || b == GV_INT64) return GV_INT64; return GV_INT32; } inline LVariantType ComparePrecision(LVariantType a, LVariantType b) { if (a == GV_NULL || b == GV_NULL) return GV_NULL; if (a == GV_DATETIME && b == GV_DATETIME) return GV_DATETIME; if (a == GV_DOUBLE || b == GV_DOUBLE) return GV_DOUBLE; if (a == GV_STRING || b == GV_STRING) return GV_STRING; if (a == GV_INT64 || b == GV_INT64) return GV_INT64; return GV_INT32; } inline char *CastString(LVariant *v, LVariant &cache) { if (v->Type == GV_STRING) return v->Str(); cache = *v; return cache.CastString(); } inline int CompareVariants(LVariant *a, LVariant *b) { // Calculates "a - b" switch (ComparePrecision(a->Type, b->Type)) { case GV_DATETIME: return a->Value.Date->Compare(b->Value.Date); break; case GV_DOUBLE: { double d = a->CastDouble() - b->CastDouble(); if (d < -MATRIX_DOUBLE_EPSILON) return -1; return d > MATRIX_DOUBLE_EPSILON; } case GV_STRING: { LVariant as, bs; char *A = CastString(a, as); char *B = CastString(b, bs); if (!A || !B) return -1; else return strcmp(A, B); break; } case GV_INT64: { int64 d = a->CastInt64() - b->CastInt64(); if (d < 0) return -1; return d > 0; } case GV_NULL: { // One or more values is NULL if (a->IsNull() && b->IsNull()) return 0; // The same.. LVariant *Val = a->IsNull() ? b : a; if (Val->IsNull()) { LAssert(0); return 0; } switch (Val->Type) { case GV_INT32: case GV_INT64: return Val->CastInt64() != 0; case GV_STRING: return Val->Str() != NULL; default: return Val->CastVoidPtr() != NULL; } break; } default: return a->CastInt32() - b->CastInt32(); break; } } LExecutionStatus LExternFunc::Call(LScriptContext *Ctx, LScriptArguments &Args) { if (!Lib || !Method) return ScriptError; LStream *Log = Ctx ? Ctx->GetLog() : NULL; if (Args.Length() != ArgType.Length()) { if (Log) Log->Print("Error: Extern '%s.%s' expecting %i arguments, not %i given.\n", Lib.Get(), Method.Get(), ArgType.Length(), Args.Length()); return ScriptError; } LArray Val; LArray Mem; bool UnsupportedArg = false; Val.Length(Args.Length() << 1); LPointer Ptr; Ptr.ni = &Val[0]; for (unsigned i=0; !UnsupportedArg && iCastVoidPtr(); *Ptr.vp++ = cp; } else { char *s = NewStr(v->CastString()); if (!s) { UnsupportedArg = true; break; } Mem.Add(s); *Ptr.vp++ = s; } break; } case GV_VOID_PTR: { *Ptr.vp++ = v->CastVoidPtr(); break; } default: { UnsupportedArg = true; break; } } } else { // Plain type switch (t.Base) { case GV_INT32: { #if defined(_WIN64) *Ptr.s64++ = v->CastInt32(); #else *Ptr.s32++ = v->CastInt32(); #endif break; } case GV_INT64: { *Ptr.s64++ = v->CastInt64(); break; } default: { UnsupportedArg = true; break; } } } } LLibrary Library(Lib); if (!Library.IsLoaded()) { if (Log) Log->Print("Error: Extern library '%s' missing.\n", Lib.Get()); return ScriptError; } void *c = Library.GetAddress(Method); if (!c) { if (Log) Log->Print("Error: Extern library '%s' has no method '%s'.\n", Lib.Get(), Method.Get()); return ScriptError; } #if defined(_MSC_VER) || (defined(MAC) && defined(LGI_32BIT) && !LGI_COCOA) ssize_t a = Ptr.ni - &Val[0]; #endif NativeInt r = 0; #if defined(_MSC_VER) #if defined(_WIN64) // 64bit... boooo no inline asm! void *b = &Val[0]; r = CallExtern64(c, &r, (uint32_t)a, b); #else // 32bit... yay inline asm! void *b = Ptr.ni - 1; _asm { mov ecx, a mov ebx, b } label1: _asm { push [ebx] sub ebx, 4 loop label1 mov ebx, c call ebx mov r, eax } #endif #elif defined(MAC) #if LGI_COCOA #warning FIXME #elif LGI_32BIT // 32bit only void *b = Ptr.ni - 1; asm ( "movl %2, %%ecx;" "movl %3, %%ebx;" "label1:" "pushl (%%ebx);" "subl %%ebx, 4;" "loop label1;" "call *%1;" :"=a"(r) /* output */ :"r"(c), "r"(a), "r"(b) /* input */ :/*"%eax",*/ "%ecx", "%ebx" /* clobbered register */ ); #endif #else // Not implemented, gcc??? LAssert(0); #endif *Args.GetReturn() = (int) r; for (unsigned i=0; iType == GV_BINARY && v->Value.Binary.Data != NULL && t.Base == GV_STRING) { // Cast the type back to t.Base char *p = (char*)v->Value.Binary.Data; v->Type = t.Base; v->Value.String = p; } } } Mem.DeleteArrays(); return ScriptSuccess; } struct CodeBlock { unsigned SrcLine; LArray AsmAddr; unsigned ViewLine; LAutoString Source; int SrcLines; LAutoString Asm; int AsmLines; }; class LVirtualMachinePriv : public LRefCount { LVariant ArrayTemp; char *CastArrayIndex(LVariant *Idx) { if (Idx == NULL || Idx->Type == GV_NULL) return NULL; if (Idx->Type == GV_STRING) return Idx->Str(); ArrayTemp = *Idx; return ArrayTemp.CastString(); } public: struct StackFrame { uint32_t CurrentFrameSize; ssize_t PrevFrameStart; size_t ReturnIp; LVarRef ReturnValue; }; enum RunType { RunContinue, RunStepInstruction, RunStepLine, RunStepOut }; LVirtualMachine *Vm; LStream *Log = NULL; LCompiledCode *Code = NULL; LExecutionStatus Status = ScriptNotStarted; LScriptPtr c; LVariant Reg[MAX_REGISTER]; LArray Locals; LVariant *Scope[SCOPE_MAX]; LArray Frames; RunType StepType; LVmCallback *Callback = NULL; LVmDebugger *Debugger = NULL; LScriptArguments *ArgsOutput = NULL; LArray BreakPts; LString TempPath; bool DebuggerEnabled = false; bool BreakCpp = false; LVirtualMachinePriv(LVirtualMachine *vm, LVmCallback *callback) { Vm = vm; Callback = callback; ZeroObj(Scope); } ~LVirtualMachinePriv() { } void DumpVariant(LStream *Log, LVariant &v) { if (!Log) return; switch (v.Type) { case GV_INT32: Log->Print("(int) %i", v.Value.Int); break; case GV_INT64: Log->Print("(int64) %I64i", v.Value.Int64); break; case GV_STRING: { char *nl = strchr(v.Value.String, '\n'); if (nl) Log->Print("(string) '%.*s...' (%i bytes)", nl - v.Value.String, v.Value.String, strlen(v.Value.String)); else Log->Print("(string) '%s'", v.Value.String); break; } case GV_DOUBLE: Log->Print("(double) %g", v.Value.Dbl); break; case GV_BOOL: Log->Print("(bool) %s", v.Value.Bool ? "true" : "false"); break; case GV_DOM: Log->Print("(LDom*) %p", v.Value.Dom); break; case GV_HASHTABLE: { Log->Print("(GHashTable*) %p {", v.Value.Hash); int n = 0; // const char *k; // for (LVariant *p = v.Value.Hash->First(&k); p; p = v.Value.Hash->Next(&k), n++) for (auto it : *v.Value.Hash) { Log->Print("%s\"%s\"=", n?",":"", it.key); DumpVariant(Log, *it.value); } Log->Print("}"); break; } case GV_LIST: { Log->Print("(LList*) %p {", v.Value.Lst); int n=0; for (auto i: *v.Value.Lst) { Log->Print("%s%i=", n?",":"", n); DumpVariant(Log, *i); n++; } Log->Print("}"); break; } case GV_NULL: { Log->Print("null"); break; } case GV_BINARY: { Log->Print("(Binary[%i])", v.Value.Binary.Length); if (v.Value.Binary.Data) { int i; for (i=0; i<16 && i < v.Value.Binary.Length; i++) Log->Print(" %.2x", ((uint8_t*)v.Value.Binary.Data)[i]); if (i < v.Value.Binary.Length) Log->Print("..."); } break; } default: Log->Print("(Type-%i) ????", v.Type); break; } } void DumpVariables(LVariant *v, int len) { if (!Log) return; for (int i=0; iPrint("[%i] = ", i); DumpVariant(Log, v[i]); Log->Print("\n"); } } } void OnException(const char *File, int Line, ssize_t Address, const char *Msg) { if (!Code) { LgiTrace("%s:%i - Exception without Code object: %s:%i, %s\n", _FL, File, Line, Msg); return; } if (Address < 0) { uint8_t *Base = &Code->ByteCode[0]; Address = c.u8 - Base; } if (!File || Line < 0) { // Extract the file / line from the current script location File = Code->GetFileName(); Line = Code->ObjectToSourceAddress(Address); } if (Log) { char *Last = strrchr((char*)File, DIR_CHAR); Log->Print("%s Exception: %s (%s:%i)\n", Code->AddrToSourceRef(Address), Msg, Last?Last+1:File, Line); } else { LgiTrace("%s:%i - Exception @ %i: %s\n", File, Line, Address, Msg); } + + LStringPipe AsmBuf; + Decompile(Code->UserContext, Code, &AsmBuf); + auto Asm = AsmBuf.NewLStr(); - if (Vm && Vm->OpenDebugger(Code)) + if (Vm && Vm->OpenDebugger(Code, Asm)) { + LAssert(Debugger->GetCode()); // It should have taken a copy of the Code we passed in + + /* if (!Debugger->GetCode()) { LAutoPtr Cp(new LCompiledCode(*Code)); Debugger->OwnCompiledCode(Cp); LStringPipe AsmBuf; Decompile(Code->UserContext, Code, &AsmBuf); LAutoString Asm(AsmBuf.NewStr()); Debugger->SetSource(Asm); } + */ Debugger->OnAddress(Address); LString m; m.Printf("%s (%s:%i)", Msg, LGetLeaf(File), Line); Debugger->OnError(m); Debugger->Run(); } else { // Set the script return value to FALSE if (Frames.Length()) { StackFrame Sf = Frames[0]; LVarRef &Ret = Sf.ReturnValue; LVariant *RetVar = &Scope[Ret.Scope][Ret.Index]; *RetVar = false; } // Exit the script c.u8 = Code->ByteCode.AddressOf() + Code->ByteCode.Length(); // Set the script status... Status = ScriptError; } } LExecutionStatus Decompile(LScriptContext *Context, LCompiledCode *Code, LStream *log) { LExecutionStatus Status = ScriptSuccess; LAssert(sizeof(LVarRef) == 4); LScriptPtr c; uint8_t *Base = &Code->ByteCode[0]; c.u8 = Base; uint8_t *e = c.u8 + Code->ByteCode.Length(); LStream *OldLog = Log; if (log) Log = log; for (unsigned k=0; kGlobals.Length(); k++) { Log->Print("G%i = ", k); DumpVariant(Log, Code->Globals[k]); Log->Print("\n"); } Log->Print("\n"); LHashTbl, char*> Fn; for (unsigned m=0; mMethods.Length(); m++) { LFunctionInfo *Info = Code->Methods[m]; if (Info->ValidStartAddr()) Fn.Add(Info->GetStartAddr(), Info->GetName()); else LAssert(!"Method not defined."); } int OldLineNum = 0; while (c.u8 < e) { char *Meth = Fn.Find(CurrentScriptAddress); if (Meth) { Log->Print("%s:\n", Meth); } int LineNum = Code->ObjectToSourceAddress(CurrentScriptAddress); if (LineNum >= 0 && LineNum != OldLineNum) { Log->Print(" %i:\n", OldLineNum = LineNum); } switch (*c.u8++) { #define VM_DECOMP 1 #include "Instructions.h" #undef VM_DECOMP } } if (log) Log = OldLog; return Status; } LExecutionStatus Setup(LCompiledCode *code, uint32_t StartOffset, LStream *log, LFunctionInfo *Func, LScriptArguments *Args) { Status = ScriptSuccess; Code = code; if (!Code) return ScriptError; if (log) Log = log; else if (Code->SysContext && Code->SysContext->GetLog()) Log = Code->SysContext->GetLog(); else if (Code->UserContext && Code->UserContext->GetLog()) Log = Code->UserContext->GetLog(); // else LgiTrace("%s:%i - Execution without a log?\n", _FL); LAssert(sizeof(LVarRef) == 4); uint8_t *Base = c.u8 = &Code->ByteCode[0]; uint8_t *e = c.u8 + Code->ByteCode.Length(); Scope[SCOPE_REGISTER] = Reg; Scope[SCOPE_LOCAL] = NULL; Scope[SCOPE_GLOBAL] = &Code->Globals[0]; Scope[SCOPE_OBJECT] = NULL; Scope[SCOPE_RETURN] = Args->GetReturn(); #ifdef _DEBUG const char *SourceFileName = Code->GetFileName(); char Obj[MAX_PATH_LEN]; if (SourceFileName) { if (strchr(SourceFileName, DIR_CHAR)) strcpy_s(Obj, sizeof(Obj), SourceFileName); else if (TempPath != NULL) LMakePath(Obj, sizeof(Obj), TempPath, SourceFileName); else { LGetSystemPath(LSP_TEMP, Obj, sizeof(Obj)); LMakePath(Obj, sizeof(Obj), Obj, SourceFileName); } char *Ext = LGetExtension(Obj); if (Ext) strcpy_s(Ext, sizeof(Obj)-(Ext-Obj), "asm"); else strcat_s(Obj, sizeof(Obj), ".asm"); } else { LAutoString DataPath; if (Code->UserContext) DataPath = Code->UserContext->GetDataFolder(); if (!DataPath) { char p[256]; if (LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p))) DataPath.Reset(NewStr(p)); } LMakePath(Obj, sizeof(Obj), DataPath, "Script.asm"); } { LDirectory SrcD, ObjD; bool OutOfDate = true; if (LFileExists(SourceFileName) && SrcD.First(SourceFileName, NULL) != 0 && ObjD.First(Obj, NULL) != 0) { OutOfDate = ObjD.GetLastWriteTime() < SrcD.GetLastWriteTime(); } if (OutOfDate || Debugger) { LFile f; LStringPipe p; LStream *Out = NULL; if (Debugger) { Out = &p; } else if (f.Open(Obj, O_WRITE)) { f.SetSize(0); Out = &f; } if (Out) { LExecutionStatus Decomp = Decompile(Code->UserContext, Code, Out); f.Close(); if (Decomp != ScriptSuccess) { LAssert(!"Decompilation failed."); return ScriptError; } if (Debugger) { - LAutoString a(p.NewStr()); + auto a = p.NewLStr(); Debugger->OnAddress(CurrentScriptAddress); Debugger->SetSource(a); } } } } #endif #if TIME_INSTRUCTIONS LARGE_INTEGER freq = {0}, start, end; QueryPerformanceFrequency(&freq); LHashTbl, int64> Timings; LHashTbl, int> TimingFreq; #endif // Calling a function only, not the whole script StackFrame &Sf = Frames.New(); Sf.ReturnIp = e - c.u8; Sf.PrevFrameStart = 0; Sf.ReturnValue.Scope = SCOPE_RETURN; Sf.ReturnValue.Index = 0; // array is only one item long anyway if (Func) { // Set up stack for function call if (!Func->ValidFrameSize()) { Log->Print("%s:%i - Function '%s' has an invalid frame size. (Script: %s).\n", _FL, Func->GetName(), Code->AddrToSourceRef(Func->GetStartAddr())); return ScriptError; } Sf.CurrentFrameSize = Func->GetFrameSize(); AddLocalSize(Sf.CurrentFrameSize); if (Args) { // Check the local frame size is at least big enough for the args... if (Args->Length() > Sf.CurrentFrameSize) { Log->Print("%s:%i - Arg count mismatch, Supplied: %i, FrameSize: %i (Script: %s).\n", _FL, (int)Args->Length(), (int)Sf.CurrentFrameSize, Code->AddrToSourceRef(Func->GetStartAddr())); return ScriptError; } // Put the arguments of the function call into the local array for (unsigned i=0; iLength(); i++) { Locals[LocalsBase+i] = *(*Args)[i]; } } if (!Func->ValidStartAddr()) { Log->Print("%s:%i - Function '%s' is not defined. (Script: %s).\n", _FL, Func->GetName(), Code->AddrToSourceRef(Func->GetStartAddr())); return ScriptError; } // Set IP to start of function c.u8 = Base + Func->GetStartAddr(); } else { // Executing body of script Sf.CurrentFrameSize = 0; if (StartOffset > 0) c.u8 = Base + StartOffset; } return Status; } int NearestLine(size_t Addr) { int l = Code->Debug.Find(Addr); if (l >= 0) return l; for (int Off = 1; Off < 20; Off++) { int l = Code->Debug.Find(Addr + Off); if (l >= 0) { return l; } l = Code->Debug.Find(Addr - Off); if (l >= 0) { return l; } } return -1; } LExecutionStatus Run(RunType Type) { LAssert(Code != NULL); uint8_t *Base = &Code->ByteCode[0]; uint8_t *e = Base + Code->ByteCode.Length(); if (Type == RunContinue && BreakPts.Length() == 0) { // Unconstrained execution while (c.u8 < e) { #if TIME_INSTRUCTIONS uint8 TimedOpCode = *c.u8; QueryPerformanceCounter(&start); #endif #ifdef BREAK_POINT if (c.u8 - Base == BREAK_POINT) { int asd=0; } #endif switch (*c.u8++) { #define VM_EXECUTE 1 #include "Instructions.h" #undef VM_EXECUTE } #if TIME_INSTRUCTIONS QueryPerformanceCounter(&end); int Ticks = end.QuadPart - start.QuadPart; int64 i = Timings.Find(TimedOpCode); Timings.Add(TimedOpCode, i + Ticks); i = TimingFreq.Find(TimedOpCode); TimingFreq.Add(TimedOpCode, i + 1); #endif } if (Log) { #if TIME_INSTRUCTIONS Log->Print("\nTimings:\n"); Log->Print("%-20s%-10s%-10s%-10s\n", "Instr", "Total", "Freq", "Ave"); int Op; for (int64 t=Timings.First(&Op); t; t=Timings.Next(&Op)) { int Frq = TimingFreq.Find(Op); int MilliSec = t * 1000000 / freq.QuadPart; Log->Print("%-20s%-10i%-10i%-10i\n", InstToString((GInstruction)Op), MilliSec, Frq, MilliSec / Frq); } Log->Print("\n"); #endif #if POST_EXECUTE_STATE Log->Print("Stack:\n"); char *v; for (void *i=Code->Globals.Lut.First(&v); i; i=Code->Globals.Lut.Next(&v)) { int Idx = (int)i - 1; if (Idx >= 0 && Idx < Code->Globals.Length()) { Log->Print("%s = ", v); DumpVariant(Log, Code->Globals[Idx]); Log->Print("\n"); } } Log->Print("\nRegisters:\n"); DumpVariables(Reg, MAX_REGISTER); #endif } } else { // Stepping through code int Param = 0; switch (Type) { case RunStepLine: Param = NearestLine(CurrentScriptAddress); break; case RunStepOut: Param = (int)Frames.Length(); break; default: break; } if (BreakCpp) #if defined(WIN32) && !defined(_WIN64) _asm int 3 #else assert(!"BreakPoint"); #endif while (c.u8 < e) { if (Type == RunContinue && BreakPts.HasItem(c.u8 - Base)) break; switch (*c.u8++) { #define VM_EXECUTE 1 #include "Instructions.h" #undef VM_EXECUTE } if (Type == RunContinue) continue; if (Type == RunStepLine) { int CurLine = NearestLine(CurrentScriptAddress); if (CurLine && CurLine != Param) break; } else if (Type == RunStepOut) { if ((int)Frames.Length() < Param) break; } else if (Type == RunStepInstruction) { break; } else LAssert(!"Invalid Type."); } } if (Debugger && Status != ScriptError) Debugger->OnAddress(CurrentScriptAddress); return Status; } }; bool LVirtualMachine::BreakOnWarning = false; LVirtualMachine::LVirtualMachine(LVmCallback *callback) { d = new LVirtualMachinePriv(this, callback); d->IncRef(); } LVirtualMachine::LVirtualMachine(Context ctx) { d = new LVirtualMachinePriv(this, ctx.Callback); d->IncRef(); if ((d->Code = ctx.Code)) { if (d->Code->ByteCode.IdxCheck(ctx.Addr)) d->c.u8 = d->Code->ByteCode.AddressOf() + ctx.Addr; } else d->c.u8 = NULL; } LVirtualMachine::LVirtualMachine(LVirtualMachine *vm) { d = vm->d; d->IncRef(); } LVirtualMachine::~LVirtualMachine() { if (d->Vm == this) d->Vm = NULL; d->DecRef(); } void LVirtualMachine::OnException(const char *File, int Line, ssize_t Address, const char *Msg) { d->OnException(File, Line, Address, Msg); } LExecutionStatus LVirtualMachine::Execute(LCompiledCode *Code, uint32_t StartOffset, LStream *Log, bool StartImmediately, LVariant *Return) { if (!Code) return ScriptError; LScriptArguments Args(this, Return); LExecutionStatus s = d->Setup(Code, StartOffset, Log, NULL, &Args); if (s != ScriptSuccess || !StartImmediately) return s; return d->Run(LVirtualMachinePriv::RunContinue); } LExecutionStatus LVirtualMachine::ExecuteFunction(LCompiledCode *Code, LFunctionInfo *Func, LScriptArguments &Args, LStream *Log, LScriptArguments *ArgsOut) { LCompiledCode *Cc = dynamic_cast(Code); if (!Cc || !Func) return ScriptError; LExecutionStatus s = d->Setup(Cc, 0, Log, Func, &Args); if (s != ScriptSuccess) return s; d->ArgsOutput = ArgsOut; auto Prev = Args.Vm; Args.Vm = this; LExecutionStatus r = d->Run(LVirtualMachinePriv::RunContinue); Args.Vm = Prev; return r; } void LVirtualMachine::SetDebuggerEnabled(bool b) { d->DebuggerEnabled = b; } LVmDebugger *LVirtualMachine::OpenDebugger(LCompiledCode *Code, const char *Assembly) { - if (d->DebuggerEnabled && !d->Debugger) + if (d->DebuggerEnabled) { if (!d->Callback) return NULL; d->Debugger = d->Callback->AttachVm(this, Code, Assembly); } return d->Debugger; } bool LVirtualMachine::StepInstruction() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunStepInstruction); return s != ScriptError; } bool LVirtualMachine::StepLine() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunStepLine); return s != ScriptError; } bool LVirtualMachine::StepOut() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunStepOut); return s != ScriptError; } bool LVirtualMachine::BreakExecution() { return false; } bool LVirtualMachine::Continue() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunContinue); return s != ScriptError; } bool LVirtualMachine::BreakPoint(const char *File, int Line, bool Add) { return false; } bool LVirtualMachine::BreakPoint(int Addr, bool Add) { if (Add) d->BreakPts.Add(Addr); else d->BreakPts.Delete(Addr); return true; } void LVirtualMachine::SetBreakCpp(bool Brk) { d->BreakCpp = Brk; } LVirtualMachine::Context LVirtualMachine::SaveContext() { LVirtualMachine::Context ctx; ctx.Callback = d->Callback; if ((ctx.Code = d->Code) && ctx.Code->ByteCode.PtrCheck(d->c.u8)) { ctx.Addr = d->c.u8 - ctx.Code->ByteCode.AddressOf(); } return ctx; } LVmCallback *LVirtualMachine::GetCallback() { return d->Callback; } void LVirtualMachine::SetTempPath(const char *Path) { d->TempPath = Path; } //////////////////////////////////////////////////////////////////// /* bool GTypeDef::GetVariant(const char *Name, LVariant &Value, char *Arr) { GMember *m = Members.Find(Name); if (!m || !Object) { LAssert(!"No member?"); return false; } GPtr p; p.i8 = Object; p.i8 += m->Offset; switch (m->Type) { case GV_INT32: { Value = *p.i32; break; } case GV_DOUBLE: { Value = *p.dbl; break; } case GV_STRING: { Value = p.i8; break; } case GV_CUSTOM: { Value.Empty(); Value.Type = GV_CUSTOM; Value.Value.Custom.Dom = m->Nest; Value.Value.Custom.Data = p.i8; break; } default: { return false; } } return true; } bool GTypeDef::SetVariant(const char *Name, LVariant &Value, char *Arr) { GMember *m = Members.Find(Name); if (!m || !Object) { LAssert(!"No member?"); return false; } GPtr p; p.i8 = Object; p.i8 += m->Offset; switch (m->Type) { case GV_INT32: { *p.i32 = Value.CastInt32(); break; } case GV_DOUBLE: { *p.dbl = Value.CastDouble(); break; } case GV_STRING: { char *s = Value.CastString(); if (!s) return false; int i; for (i = 0; *s && i < m->Size - 1; i++) { *p.i8++ = *s++; } if (i < m->Size - 1) *p.i8 = 0; break; } case GV_CUSTOM: { GTypeDef *t = dynamic_cast(Value.Value.Custom.Dom); if (m->Nest == t) { memcpy(p.i8, Value.Value.Custom.Data, t->Sizeof()); } break; } default: { return false; } } return true; } */ /////////////////////////////////////////////////////////////////////////////////////////////////////////////// uint32_t IconsData[] = { 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9D9CCEBE, 0x3B166419, 0x74594357, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x543CF81F, 0xCEDE647C, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7C998D1B, 0xF81FB61C, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCEBFF81F, 0x43DB4C1C, 0xDF1E955B, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8C0CF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D5CF81F, 0x43595C1A, 0x3AF74338, 0x8CFA4B57, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xC69D6C39, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0x647BADFD, 0x543C53FB, 0x3B1553FB, 0x329132B2, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F64CB, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0x8D9F8D9F, 0x855E857E, 0x7CFD7D1D, 0x74BC74DC, 0xF81F74DC, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8BEBF81F, 0xF81F83AB, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7CBB8D5C, 0xF81FB63D, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F5BD8, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x959D647C, 0xCEBDF81F, 0x32913AD3, 0xB61B5353, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x3BA564CB, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0x74DC8D9F, 0x8D9FF81F, 0x74DC8D9F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9F8D9F, 0x855E857E, 0x7CFD7D1D, 0x74BC74DC, 0xF81F74DC, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0xAE1EB65E, 0xD71FA5FE, 0x853D8D7D, 0x6CBD7CFD, 0xF81F2AB5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8BEBF81F, 0x7329FF98, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE9E5C1A, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F5398, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F6C7C, 0x5BD6F81F, 0xD6DD5BB5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB6D45CAA, 0xF81F3BA5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0x2AB5D71F, 0x8D9FF81F, 0x2AB5D71F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xD71F8D9F, 0xC6DFD71F, 0xB65FBE7F, 0x9DDEAE3F, 0xF81F2AB5, 0xF81FF81F, 0xF81FF81F, 0x857EF81F, 0x9DDEAE1E, 0xFFFFDF3F, 0x74FD853D, 0x647C6CBC, 0xF81F2274, 0xF81FF81F, 0xF81FF81F, 0x9C6CF81F, 0x944D944C, 0x944D8C0C, 0xFF54FF76, 0xF81F62A8, 0xF81FF81F, 0xF81FF81F, 0xBE5D4B99, 0x543CF81F, 0xC6BE5C7C, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F5398, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F53DB, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x75ED5489, 0x3BA5B6D4, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x857EF81F, 0x2274DF3F, 0x857EF81F, 0x2274DF3F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xDF3F857E, 0xB65FCEDF, 0x9DBEAE1F, 0x851B959E, 0xF81F2274, 0xF81FF81F, 0xF81FF81F, 0x855EF81F, 0xE75F9DDE, 0xFFFFFFFF, 0x6CBC74DD, 0x543C647C, 0xF81F1A33, 0xF81FF81F, 0xF81FF81F, 0x944BF81F, 0xFFD9F756, 0xFF53FF97, 0xFEEEFF31, 0x5226F66A, 0xF81FF81F, 0xF81FF81F, 0x84DA6C3A, 0xBE7EADDC, 0x43DB4C1C, 0xF81F953B, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F4B77, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0x9D7C5C3B, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x75ED4C48, 0x5D0A75ED, 0xF81F3BA5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x855EF81F, 0x1A33D71F, 0x855EF81F, 0x1A33D71F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xD71F855E, 0xA5FFBE7F, 0x855E959E, 0x6C7A7CFD, 0xF81F1A33, 0xF81FF81F, 0xF81FF81F, 0x7D1DF81F, 0xFFFFDF1E, 0xFFFFFFFF, 0xFFFFFFFF, 0x4BFCC69D, 0xF81F11F2, 0xF81FF81F, 0xF81FF81F, 0x940AF81F, 0xFF75FF97, 0xFEEEFF31, 0xF6ABFECD, 0xBCA7E5E8, 0xF81F49C5, 0xF81FF81F, 0x7CBAB61C, 0x541C53FA, 0x3B1553FB, 0x329132B2, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F4B57, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x6C9CB63D, 0x43584BBA, 0x3AD53B16, 0x743742F4, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x6D8B4407, 0x3C055D0A, 0x1B212B84, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7D1DF81F, 0x11F2CEBF, 0x7D1DF81F, 0x11F2CEBF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCEBF7D1D, 0x9DBEAE3F, 0x7CFD8D5E, 0x53B76CBC, 0xF81F11F2, 0xF81FF81F, 0xF81FF81F, 0x7CFDF81F, 0xCEBD853D, 0xFFFFFFFF, 0x541C5C5C, 0x43BBFFFF, 0xF81F09B1, 0xF81FF81F, 0xF81FF81F, 0x83A9F81F, 0xFF31FF74, 0xF6ACF6CF, 0xDDEAF68B, 0x41A4B467, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xC69DF81F, 0x32913AD3, 0xB5FB4B53, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F4336, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5D0A33E5, 0x2B843C05, 0xF81F0B00, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7CFDF81F, 0x09B1BE9F, 0x7CFDF81F, 0x09B1BE9F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xBE9F7CFD, 0x959EA5FF, 0x6C9C7CFD, 0x4B565C3B, 0xF81F09B1, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x6CBC74FD, 0xFFFFBE3B, 0x43DC541C, 0x337BFFFF, 0xF81F0990, 0xF81FF81F, 0xF81FF81F, 0x8389F81F, 0x6AA472E7, 0x72C56264, 0xB487DDA9, 0xF81F41A4, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5BD6F81F, 0xF81F5BB5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F4336, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x3C052BA4, 0x0B002B84, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x0990AE5F, 0x74DCF81F, 0x0990AE5F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xAE5F74DC, 0x7D1D95BE, 0x5C3B6CBC, 0x43354BD9, 0xF81F0990, 0xF81FF81F, 0xF81FF81F, 0x74BCF81F, 0x647C6CBC, 0x9D7A543C, 0x3B9B43DB, 0x2B5BFFFF, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5A45F81F, 0x41A4AC26, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F5377, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x2B842363, 0xF81F0B00, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x74BCF81F, 0x0170A5FE, 0x74BCF81F, 0x0170A5FE, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xA5FE74BC, 0x6C79851A, 0x4B555BF8, 0x32D34314, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x543C5C7C, 0x43BB4BFC, 0x335B3B9B, 0x231BFFFF, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x49E4F81F, 0xF81F41A4, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9D7A53B7, 0x4BBAF81F, 0xB61C6459, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0B001B42, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x01700170, 0x74DCF81F, 0x01700170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x3B1674DC, 0x2A9432F6, 0x11F22253, 0x017009B1, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x3B163B16, 0x2A9432F6, 0x11F22253, 0x017009B1, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x41A4F81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0x747863F7, 0x953BB61C, 0x4BB843B9, 0xB61B7C98, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F1301, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x6C18ADBA, 0x53D953B7, 0x3B144B98, 0x32503291, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB61BF81F, 0x32503291, 0xA5794B12, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5B95F81F, 0xB61A5373, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, }; LInlineBmp DbgIcons = {128, 16, 16, IconsData}; enum DbgCtrls { IDC_STATIC = -1, IDC_TABS = 300, IDC_BOX, IDC_BOX2, IDC_TEXT, IDC_LOCALS, IDC_GLOBALS, IDC_REGISTERS, IDC_STACK, IDC_LOG, IDC_RUN, IDC_PAUSE, IDC_STOP, IDC_RESTART, IDC_GOTO, IDC_STEP_INSTR, IDC_STEP_LINE, IDC_STEP_OUT, IDC_SOURCE_LST, IDC_BREAK_POINT, IDC_BREAK_CPP, IDC_VARS_TBL }; struct LScriptVmDebuggerPriv; class LDebugView : public LTextView3 { LScriptVmDebuggerPriv *d; int CurLine; int ErrorLine; LString Error; LArray BreakPts; public: LDebugView(LScriptVmDebuggerPriv *priv); ~LDebugView(); void SetError(const char *Err); int GetCurLine() { return CurLine; } int GetAddr(); void ScrollToCurLine(); void PourText(size_t Start, ssize_t Length) override; void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) override; void OnPaint(LSurface *pDC) override; bool Breakpoint(int Addr); }; struct LScriptVmDebuggerPriv { // Current script - bool OwnVm; LAutoPtr Vm; - LVmCallback *Callback; + LAutoPtr Obj; + + LVmCallback *Callback = NULL; LString Script, Assembly; LArray Blocks; - size_t CurrentAddr; + size_t CurrentAddr = -1; LArray LineIsAsm; - LAutoPtr Obj; LVariant Return; - bool AcceptNotify; + bool AcceptNotify = false; // Ui - bool RunLoop; - LView *Parent; - LBox *Main; - LBox *Sub; - LList *SourceLst; - LTabView *Tabs; - LDebugView *Text; - LList *Locals, *Globals, *Registers, *Stack; - LTextLog *Log; - LToolBar *Tools; - LTableLayout *VarsTbl; - - LScriptVmDebuggerPriv() - { - RunLoop = false; - OwnVm = false; - CurrentAddr = -1; - Main = NULL; - Tabs = NULL; - Log = NULL; - Text = NULL; - Locals = NULL; - Globals = NULL; - Registers = NULL; - Stack = NULL; - Tools = NULL; - SourceLst = NULL; - Callback = NULL; - VarsTbl = NULL; - } + LView *Parent = NULL; + LBox *Main = NULL; + LBox *Sub = NULL; + LList *SourceLst = NULL; + LTabView *Tabs = NULL; + LDebugView *Text = NULL; + LList *Locals = NULL, *Globals = NULL, *Registers = NULL, *Stack = NULL; + LTextLog *Log = NULL; + LToolBar *Tools = NULL; + LTableLayout *VarsTbl = NULL; }; LDebugView::LDebugView(LScriptVmDebuggerPriv *priv) : LTextView3(IDC_TEXT, 0, 0, 100, 100) { d = priv; ErrorLine = -1; SetWrapType(TEXTED_WRAP_NONE); GetCss(true)->PaddingLeft(LCss::Len(LCss::LenPx, 18)); } LDebugView::~LDebugView() { } void LDebugView::SetError(const char *Err) { ErrorLine = CurLine; Error = Err; } #define IsHexChar(c) \ ( \ IsDigit(c) \ || \ ((c) >= 'a' && (c) <= 'f') \ || \ ((c) >= 'A' && (c) <= 'F') \ ) int IsAddr(char16 *Ln) { int Addr = 0; for (char16 *s = Ln; *s && *s != '\n' && s < Ln + 8; s++) { Addr += IsHexChar(*s); } if (Addr != 8) return -1; return HtoiW(Ln); } int LDebugView::GetAddr() { ssize_t Index; LTextLine *t = GetTextLine(Cursor, &Index); if (!t) return -1; int Addr = IsAddr(Text + t->Start); return Addr; } void LDebugView::ScrollToCurLine() { SetLine(CurLine); } bool LDebugView::Breakpoint(int Addr) { if (BreakPts.HasItem(Addr)) { BreakPts.Delete(Addr); Invalidate(); return false; } else { BreakPts.Add(Addr); Invalidate(); return true; } } void LDebugView::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { LTextView3::OnPaintLeftMargin(pDC, r, colour); pDC->Colour(LColour(192, 0, 0)); LFont *f = GetFont(); f->Colour(L_LOW, L_WORKSPACE); f->Transparent(true); int Fy = f->GetHeight(); int Start = VScroll ? (int)VScroll->Value() : 0; int Page = (r.Y() + Fy - 1) / Fy; int Ln = Start; int Rad = (Fy >> 1) - 1; int PadY = GetCss(true)->PaddingTop().ToPx(Y(), f) + ((Fy - Rad) >> 1); auto It = Line.begin(Start); for (auto i = *It; i && Ln <= Start + Page; i = *(++It), Ln++) { int OffY = (Ln - Start) * f->GetHeight(); /* LString Num; Num.Printf("%i", Ln); LDisplayString Ds(f, Num); Ds.Draw(pDC, 0, r.y1+OffY); */ char16 *s = Text + i->Start; int Addr = IsAddr(s); if (BreakPts.HasItem(Addr)) { pDC->FilledCircle(r.x1 + Rad + 2, OffY + PadY + Rad, Rad); } } f->Transparent(false); f->Colour(L_TEXT, L_WORKSPACE); } void LDebugView::OnPaint(LSurface *pDC) { LTextView3::OnPaint(pDC); if (Error) { LTextLine *Ln = Line[ErrorLine]; LFont *f = GetFont(); LRect c = GetClient(); int Pad = 3; LDisplayString Ds(f, Error); LRect r(0, 0, Ds.X()-1, Ds.Y()-1); r.Inset(-Pad, -Pad); r.Offset(c.X()-r.X(), Ln ? Ln->r.y1 - ScrollYPixel(): 0); f->Transparent(false); f->Colour(LColour::White, LColour::Red); Ds.Draw(pDC, r.x1 + Pad, r.y1 + Pad, &r); } } void LDebugView::PourText(size_t Start, ssize_t Len) { LTextView3::PourText(Start, Len); CurLine = -1; for (unsigned i=0; iBlocks.Length(); i++) { CodeBlock &b = d->Blocks[i]; for (unsigned n=0; nCurrentAddr >= b.AsmAddr[n]) { CurLine = b.ViewLine + b.SrcLines + n - 1; } } } unsigned Idx = 0; for (auto l: Line) { // char16 *t = Text + l->Start; // char16 *e = t + l->Len; if (CurLine == Idx) { l->c.Rgb(0, 0, 0); l->Back = LColour(L_DEBUG_CURRENT_LINE); } else { bool IsAsm = Idx < d->LineIsAsm.Length() ? d->LineIsAsm[Idx] : false; if (IsAsm) { l->c.Rgb(0, 0, 255); l->Back.Rgb(0xf0, 0xf0, 0xf0); } } Idx++; } } -LVmDebuggerWnd::LVmDebuggerWnd(LView *Parent, LVmCallback *Callback, LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) +LVmDebuggerWnd::LVmDebuggerWnd(LView *Parent, LVmCallback *Callback, LAutoPtr Vm, LAutoPtr Code, const char *Assembly) { d = new LScriptVmDebuggerPriv; d->Parent = Parent; - d->AcceptNotify = false; - if (Vm) - d->Vm.Reset(new LVirtualMachine(Vm)); + d->Vm = Vm; d->Callback = Callback; - if (Code) - d->Script = Code->GetSource(); + d->Obj = Code; + if (d->Obj) + d->Script = d->Obj->GetSource(); d->Assembly = Assembly; LRect r(0, 0, 1000, 900); SetPos(r); if (Parent) MoveSameScreen(Parent); else MoveToCenter(); Name("Script Debugger"); if (Attach(NULL)) { if ((Menu = new LMenu)) { Menu->Attach(this); LSubMenu *s = Menu->AppendSub("Debug"); s->AppendItem("Run", IDC_RUN, true, -1, "F5"); s->AppendItem("Pause", IDC_PAUSE, true, -1, NULL); s->AppendItem("Stop", IDC_STOP, true, -1, "Ctrl+Break"); s->AppendItem("Restart", IDC_RESTART, true, -1, NULL); s->AppendItem("Goto", IDC_GOTO, true, -1, NULL); s->AppendSeparator(); s->AppendItem("Step Instruction", IDC_STEP_INSTR, true, -1, "F11"); s->AppendItem("Step Line", IDC_STEP_LINE, true, -1, "F10"); s->AppendItem("Step Out", IDC_STEP_OUT, true, -1, "Shift+F11"); s->AppendSeparator(); s->AppendItem("Breakpoint", IDC_BREAK_POINT, true, -1, "F9"); s->AppendItem("Break Into C++", IDC_BREAK_CPP, true, -1, "Ctrl+F9"); } AddView(d->Tools = new LToolBar); uint16 *Px = (uint16*) DbgIcons.Data; LImageList *il = new LImageList(16, 16, DbgIcons.Create(*Px)); if (il) d->Tools->SetImageList(il, 16, 16, true); d->Tools->AppendButton("Run", IDC_RUN); d->Tools->AppendButton("Pause", IDC_PAUSE); d->Tools->AppendButton("Stop", IDC_STOP); d->Tools->AppendButton("Restart", IDC_RESTART); d->Tools->AppendButton("Goto", IDC_GOTO); d->Tools->AppendSeparator(); d->Tools->AppendButton("Step Instruction", IDC_STEP_INSTR); d->Tools->AppendButton("Step Line", IDC_STEP_LINE); d->Tools->AppendButton("Step Out", IDC_STEP_OUT); AddView(d->Main = new LBox(IDC_BOX)); d->Main->SetVertical(true); d->Main->AddView(d->Sub = new LBox(IDC_BOX2)); d->Sub->SetVertical(false); d->Sub->AddView(d->SourceLst = new LList(IDC_SOURCE_LST, 0, 0, 100, 100)); d->SourceLst->GetCss(true)->Width(LCss::Len("200px")); d->SourceLst->AddColumn("Source", 200); d->Sub->AddView(d->Text = new LDebugView(d)); d->Main->AddView(d->Tabs = new LTabView(IDC_TABS)); d->Tabs->GetCss(true)->Height(LCss::Len("250px")); LTabPage *p = d->Tabs->Append("Variables"); p->Append(d->VarsTbl = new LTableLayout(IDC_VARS_TBL)); int x = 0, y = 0; auto *c = d->VarsTbl->GetCell(x++, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Globals:")); c = d->VarsTbl->GetCell(x++, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Locals:")); c = d->VarsTbl->GetCell(x++, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Registers:")); x = 0; y++; c = d->VarsTbl->GetCell(x++, y); c->Add(d->Globals = new LList(IDC_GLOBALS, 0, 0, 100, 100)); d->Globals->AddColumn("Name",100); d->Globals->AddColumn("Value",400); c = d->VarsTbl->GetCell(x++, y); c->Add(d->Locals = new LList(IDC_LOCALS, 0, 0, 100, 100)); d->Locals->AddColumn("Name",100); d->Locals->AddColumn("Value",400); c = d->VarsTbl->GetCell(x++, y); c->Add(d->Registers = new LList(IDC_REGISTERS, 0, 0, 100, 100)); d->Registers->AddColumn("Name",100); d->Registers->AddColumn("Value",400); p = d->Tabs->Append("Stack"); p->Append(d->Stack = new LList(IDC_STACK, 0, 0, 100, 100)); d->Stack->SetPourLargest(true); d->Stack->AddColumn("Address", 100); d->Stack->AddColumn("Function", 300); p = d->Tabs->Append("Log"); p->Append(d->Log = new LTextLog(IDC_LOG)); AttachChildren(); Visible(true); { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), LGetExePath(), "../Scripts"); LDirectory dir; LListItem *Match = NULL; d->SourceLst->MultiSelect(false); for (int b = dir.First(p); b; b = dir.Next()) { if (!dir.IsDir()) { char *n = dir.GetName(); if (stristr(n, ".script") && dir.Path(p, sizeof(p))) { LListItem *it = new LListItem; it->SetText(dir.GetName(), 0); it->SetText(p, 1); - if (Code && Code->GetFileName()) + if (d->Obj && d->Obj->GetFileName()) { - if (_stricmp(p, Code->GetFileName()) == 0) + if (_stricmp(p, d->Obj->GetFileName()) == 0) Match = it; } d->SourceLst->Insert(it); } } } - if (!Match && Code) + if (!Match && d->Obj) { LListItem *it = new LListItem; if (it) { - it->SetText(LGetLeaf(Code->GetFileName()), 0); - it->SetText(Code->GetFileName(), 1); + it->SetText(LGetLeaf(d->Obj->GetFileName()), 0); + it->SetText(d->Obj->GetFileName(), 1); d->SourceLst->Insert(it); it->Select(true); } } } } d->AcceptNotify = true; + + if (d->Assembly) + SetSource(d->Assembly); } LVmDebuggerWnd::~LVmDebuggerWnd() { - LAssert(d->RunLoop == false); } bool LVmDebuggerWnd::OnRequestClose(bool OsShuttingDown) { - if (!d->RunLoop) - return LWindow::OnRequestClose(OsShuttingDown); - - d->RunLoop = false; - return false; // Wait for Run() to exit in it's own time. + return LWindow::OnRequestClose(OsShuttingDown); } void LVmDebuggerWnd::Run() { - // This is to allow objects on the application's stack to - // still be valid while the debugger UI is shown. - d->RunLoop = true; - while (d->RunLoop && Visible()) - { - LYield(); - LSleep(20); - } - Quit(); + Visible(true); } LStream *LVmDebuggerWnd::GetLog() { return d->Log; } -void LVmDebuggerWnd::OwnVm(bool Own) -{ - d->OwnVm = Own; -} - -void LVmDebuggerWnd::OwnCompiledCode(LAutoPtr Cc) +void LVmDebuggerWnd::SetCode(LAutoPtr Cc) { d->Obj = Cc; } LCompiledCode *LVmDebuggerWnd::GetCode() { return d->Obj; } void LVmDebuggerWnd::SetSource(const char *Mixed) { #if 1 LStringPipe Glob(256); LStringPipe Tmp(256); d->Blocks.Length(0); CodeBlock *Cur = &d->Blocks.New(); // Parse the mixed source auto t = LString(Mixed).SplitDelimit("\n", -1, false); bool InGlobals = true; int InAsm = -1; for (unsigned i=0; i Code Cur->Asm.Reset(Tmp.NewStr()); Cur = &d->Blocks.New(); } else { // Code -> Asm Tmp.Empty(); } InAsm = IsAsm; } Tmp.Print("%s\n", l); if (InAsm) { Cur->AsmLines++; Cur->AsmAddr.Add(htoi(l)); } else if (!Cur->SrcLine) { while (*l == ' ') l++; if (IsDigit(*l)) Cur->SrcLine = atoi(l); } } if (InAsm) Cur->Asm.Reset(Tmp.NewStr()); Tmp.Empty(); LStringPipe Txt; auto Src = d->Script.SplitDelimit("\n", -1, false); unsigned SrcLine = 1; unsigned ViewLine = 1; for (unsigned i=0; iBlocks.Length(); i++) { CodeBlock &b = d->Blocks[i]; if (b.SrcLine > 0) { while (SrcLine <= b.SrcLine) { char *s = Src[SrcLine-1]; Tmp.Print("%i: %s\n", SrcLine, s ? s : ""); b.SrcLines++; SrcLine++; } b.Source.Reset(Tmp.NewStr()); } if (b.Source && b.Asm) { b.ViewLine = ViewLine; ViewLine += b.SrcLines + b.AsmLines; Txt.Print("%s%s", b.Source.Get(), b.Asm.Get()); } else if (b.Source) { b.ViewLine = ViewLine; ViewLine += b.SrcLines; Txt.Print("%s", b.Source.Get()); } else if (b.Asm) { b.ViewLine = ViewLine; ViewLine += b.AsmLines; Txt.Print("%s", b.Asm.Get()); } } while (SrcLine <= Src.Length()) { Txt.Print("%i: %s\n", SrcLine, Src[SrcLine-1].Get()); SrcLine++; } for (unsigned i=0; iBlocks.Length(); i++) { CodeBlock &b = d->Blocks[i]; int Base = b.ViewLine + b.SrcLines; for (int n = Base; nLineIsAsm[n-1] = true; } LAutoString a(Txt.NewStr()); d->Text->Name(a); #else d->Text->Name(Mixed); #endif } void LVmDebuggerWnd::UpdateVariables(LList *Lst, LVariant *Arr, ssize_t Len, char Prefix) { if (!d->Vm || !Lst || !Arr) return; List all; Lst->GetAll(all); LListItem *it; for (ssize_t i=0; iVm->d->DumpVariant(&p, *v); LAutoString a(p.NewStr()); char nm[32]; sprintf_s(nm, sizeof(nm), "%c" LPrintfSSizeT, Prefix, i); if (i >= (ssize_t)all.Length()) { it = new LListItem; all.Insert(it); Lst->Insert(it); } it = i < (ssize_t)all.Length() ? all[i] : NULL; if (it) { it->SetText(nm, 0); it->SetText(a, 1); } } Lst->ResizeColumnsToContent(); } void LVmDebuggerWnd::OnAddress(size_t Addr) { d->CurrentAddr = Addr; if (d->Text) { ssize_t Sz = d->Text->Length(); d->Text->PourText(0, Sz); d->Text->ScrollToCurLine(); d->Text->Invalidate(); } OnNotify(d->Tabs, LNotifyValueChanged); } void LVmDebuggerWnd::OnError(const char *Msg) { if (Msg) d->Text->SetError(Msg); } void LVmDebuggerWnd::OnRun(bool Running) { } void LVmDebuggerWnd::LoadFile(const char *File) { if (!d->Vm || !d->Callback) { LAssert(0); return; } LFile f; if (f.Open(File, O_READ)) d->Script = f.Read(); else d->Script.Empty(); d->Obj.Reset(); if (d->Callback->CompileScript(d->Obj, File, d->Script)) { LCompiledCode *Code = dynamic_cast(d->Obj.Get()); if (Code) { d->Return.Empty(); d->Vm->d->Frames.Length(0); LScriptArguments Args(d->Vm, &d->Return); d->Vm->d->Setup(Code, 0, d->Log, NULL, &Args); } } } int LVmDebuggerWnd::OnCommand(int Cmd, int Event, OsView Wnd) { if (d->Vm && d->Vm->d->Vm == NULL) { // This happens when the original VM decides to go away and leave // our copy of the VM as the only one left. This means we have to // update the pointer in the VM's private data to point to us. d->Vm->d->Vm = d->Vm; } switch (Cmd) { case IDC_RUN: { if (d->Vm) d->Vm->Continue(); break; } case IDC_PAUSE: { if (d->Vm) d->Vm->BreakExecution(); break; } case IDC_STOP: { - d->Vm.Reset(); - if (d->RunLoop) - d->RunLoop = false; - else - Quit(); + LAssert(!"Impl me."); break; } case IDC_RESTART: { if (d->Vm && d->Obj) { LCompiledCode *Code = dynamic_cast(d->Obj.Get()); if (Code) d->Vm->Execute(Code, 0, d->Log, false); } break; } case IDC_GOTO: { break; } case IDC_STEP_INSTR: { if (d->Vm) d->Vm->StepInstruction(); break; } case IDC_STEP_LINE: { if (d->Vm) d->Vm->StepLine(); break; } case IDC_STEP_OUT: { if (d->Vm) d->Vm->StepOut(); break; } case IDC_BREAK_POINT: { int Addr = d->Text->GetAddr(); if (Addr >= 0) d->Vm->BreakPoint(Addr, d->Text->Breakpoint(Addr)); break; } case IDC_BREAK_CPP: { if (!d->Vm) { LAssert(0); break; } LMenuItem *i = Menu->FindItem(IDC_BREAK_CPP); if (!i) { LAssert(0); break; } bool b = !i->Checked(); i->Checked(b); d->Vm->SetBreakCpp(b); break; } } return LWindow::OnCommand(Cmd, Event, Wnd); } int LVmDebuggerWnd::OnNotify(LViewI *Ctrl, LNotification n) { if (!d->AcceptNotify) return 0; switch (Ctrl->GetId()) { case IDC_TABS: { switch (Ctrl->Value()) { case 0: // Variables { if (d->Obj) { UpdateVariables(d->Globals, d->Vm->d->Scope[SCOPE_GLOBAL], d->Obj->Globals.Length(), 'G'); } if (d->Vm->d->Frames.Length()) { LVirtualMachinePriv::StackFrame &frm = d->Vm->d->Frames.Last(); UpdateVariables(d->Locals, d->Vm->d->Scope[SCOPE_LOCAL], frm.CurrentFrameSize, 'L'); } else d->Locals->Empty(); UpdateVariables(d->Registers, d->Vm->d->Scope[SCOPE_REGISTER], MAX_REGISTER, 'R'); break; } case 1: // Call stack { d->Stack->Empty(); LArray &Frames = d->Vm->d->Frames; for (int i=(int)Frames.Length()-1; i>=0; i--) { LVirtualMachinePriv::StackFrame &Sf = Frames[i]; LListItem *li = new LListItem; LString s; s.Printf("%p/%i", Sf.ReturnIp, Sf.ReturnIp); li->SetText(s, 0); const char *Src = d->Vm->d->Code->AddrToSourceRef(Sf.ReturnIp); li->SetText(Src, 1); d->Stack->Insert(li); } break; } case 2: // Log { break; } } break; } case IDC_SOURCE_LST: { if (n.Type == LNotifyItemSelect) { LListItem *it = d->SourceLst->GetSelected(); if (!it) break; const char *full = it->GetText(1); if (!LFileExists(full)) break; LoadFile(full); } break; } } return LWindow::OnNotify(Ctrl, n); } LMessage::Param LVmDebuggerWnd::OnEvent(LMessage *Msg) { return LWindow::OnEvent(Msg); } diff --git a/src/common/Lgi/Variant.cpp b/src/common/Lgi/Variant.cpp --- a/src/common/Lgi/Variant.cpp +++ b/src/common/Lgi/Variant.cpp @@ -1,2405 +1,2405 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" const char *LVariant::TypeToString(LVariantType t) { switch (t) { case GV_NULL: return "NULL"; case GV_INT32: return "int32"; case GV_INT64: return "int64"; case GV_BOOL: return "bool"; case GV_DOUBLE: return "double"; case GV_STRING: return "String"; case GV_BINARY: return "Binary"; case GV_LIST: return "List"; case GV_DOM: return "Dom"; case GV_DOMREF: return "DomReference"; case GV_VOID_PTR: return "VoidPtr"; case GV_DATETIME: return "DateTime"; case GV_HASHTABLE: return "HashTable"; case GV_OPERATOR: return "Operator"; case GV_CUSTOM: return "Custom"; case GV_WSTRING: return "WString"; case GV_GVIEW: return "View"; case GV_STREAM: return "Stream"; case GV_LSURFACE: return "Surface"; case GV_LMOUSE: return "MouseEvent"; case GV_LKEY: return "KeyboardEvent"; default: return "Unknown"; } return NULL; } const char *LVariant::OperatorToString(LOperator op) { switch (op) { case OpNull: return "OpNull"; case OpAssign: return "OpAssign"; case OpPlus: return "OpPlus"; case OpUnaryPlus: return "OpUnaryPlus"; case OpMinus: return "OpMinus"; case OpUnaryMinus: return "OpUnaryMinus"; case OpMul: return "OpMul"; case OpDiv: return "OpDiv"; case OpMod: return "OpMod"; case OpLessThan: return "OpLessThan"; case OpLessThanEqual: return "OpLessThanEqual"; case OpGreaterThan: return "OpGreaterThan"; case OpGreaterThanEqual: return "OpGreaterThanEqual"; case OpEquals: return "OpEquals"; case OpNotEquals: return "OpNotEquals"; case OpPlusEquals: return "OpPlusEquals"; case OpMinusEquals: return "OpMinusEquals"; case OpMulEquals: return "OpMulEquals"; case OpDivEquals: return "OpDivEquals"; case OpPostInc: return "OpPostInc"; case OpPostDec: return "OpPostDec"; case OpPreInc: return "OpPreInc"; case OpPreDec: return "OpPreDec"; case OpAnd: return "OpAnd"; case OpOr: return "OpOr"; case OpNot: return "OpNot"; } return NULL; } LVariant::LVariant() { Type = GV_NULL; ZeroObj(Value); } LVariant::LVariant(LVariant const &v) { Type = GV_NULL; ZeroObj(Value); *this = v; } #if LVARIANT_SIZET LVariant::LVariant(size_t i) { Type = GV_NULL; *this = i; } #endif #if LVARIANT_SSIZET LVariant::LVariant(ssize_t i) { Type = GV_NULL; *this = i; } #endif LVariant::LVariant(int32_t i) { Type = GV_INT32; Value.Int = i; } LVariant::LVariant(uint32_t i) { Type = GV_INT32; Value.Int = i; } LVariant::LVariant(int64_t i) { Type = GV_INT64; Value.Int64 = i; } LVariant::LVariant(uint64_t i) { Type = GV_INT64; Value.Int64 = i; } LVariant::LVariant(double i) { Type = GV_DOUBLE; Value.Dbl = i; } LVariant::LVariant(const char *s) { Value.String = NewStr(s); Type = Value.String ? GV_STRING : GV_NULL; } LVariant::LVariant(const char16 *s) { Value.WString = NewStrW(s); Type = Value.WString ? GV_WSTRING : GV_NULL; } LVariant::LVariant(void *p) { Type = GV_NULL; *this = p; } LVariant::LVariant(LDom *p) { Type = GV_NULL; *this = p; } LVariant::LVariant(LDom *p, char *name) { Type = GV_NULL; SetDomRef(p, name); } LVariant::LVariant(const LDateTime *d) { Type = GV_NULL; *this = d; } LVariant::LVariant(LOperator Op) { Type = GV_OPERATOR; Value.Op = Op; } LVariant::~LVariant() { Empty(); } bool LVariant::operator ==(LVariant &v) { switch (Type) { default: case GV_NULL: return v.Type == Type; case GV_INT32: return Value.Int == v.CastInt32(); case GV_INT64: return Value.Int64 == v.CastInt64(); case GV_BOOL: return Value.Bool == v.CastBool(); case GV_DOUBLE: return Value.Dbl == v.CastDouble(); case GV_STRING: { char *s = v.Str(); if (Value.String && s) return !strcmp(Value.String, s); break; } case GV_WSTRING: { char16 *w = v.WStr(); if (Value.WString && w) return !StrcmpW(Value.WString, w); break; } case GV_BINARY: { if (v.Type == Type && Value.Binary.Data == v.Value.Binary.Data && Value.Binary.Length == v.Value.Binary.Length) { return true; } break; } case GV_LIST: { if (!Value.Lst || !v.Value.Lst) return false; if (Value.Lst->Length() != v.Value.Lst->Length()) return false; auto ValIt = Value.Lst->begin(); auto VIt = v.Value.Lst->begin(); LVariant *a, *b; while ( (a = *ValIt) && (b = *VIt) ) { if (!(*a == *b)) return false; ValIt++; VIt++; } return true; } case GV_DOMREF: { return Value.DomRef.Dom == v.Value.DomRef.Dom && Value.DomRef.Name != 0 && v.Value.DomRef.Name != 0 && !stricmp(Value.DomRef.Name, v.Value.DomRef.Name); } case GV_DATETIME: { if (Value.Date && v.Value.Date) { return Value.Date->Compare(v.Value.Date) == 0; } break; } case GV_DOM: return Value.Dom == v.Value.Dom; case GV_OPERATOR: return Value.Op == v.Value.Op; case GV_CUSTOM: return Value.Custom == v.Value.Custom; case GV_LSURFACE: return Value.Surface.Ptr == v.Value.Surface.Ptr; case GV_GVIEW: return Value.View == v.Value.View; /* case GV_GFILE: return Value.File.Ptr == v.Value.File.Ptr; */ case GV_STREAM: return Value.Stream.Ptr == v.Value.Stream.Ptr; case GV_LMOUSE: return Value.Mouse == v.Value.Mouse; case GV_LKEY: return Value.Key == v.Value.Key; case GV_VOID_PTR: return Value.Ptr == v.Value.Ptr; case GV_HASHTABLE: { LAssert(0); break; } } return false; } LVariant &LVariant::operator =(const LDateTime *d) { Empty(); if (d) { Type = GV_DATETIME; Value.Date = new LDateTime; if (Value.Date) { *Value.Date = *d; // if (Dirty) *Dirty = true; } } return *this; } LVariant &LVariant::operator =(bool i) { Empty(); Type = GV_BOOL; Value.Bool = i; // if (Dirty) *Dirty = true; return *this; } #if LVARIANT_SIZET LVariant &LVariant::operator =(size_t i) { Empty(); #if LGI_64BIT Type = GV_INT64; Value.Int64 = i; #else Type = GV_INT32; Value.Int = i; #endif return *this; } #endif #if LVARIANT_SSIZET LVariant &LVariant::operator =(ssize_t i) { Empty(); #if LGI_64BIT Type = GV_INT64; Value.Int64 = i; #else Type = GV_INT32; Value.Int = i; #endif return *this; } #endif LVariant &LVariant::operator =(int32 i) { Empty(); Type = GV_INT32; Value.Int = i; return *this; } LVariant &LVariant::operator =(uint32_t i) { Empty(); Type = GV_INT32; Value.Int = i; return *this; } LVariant &LVariant::operator =(int64_t i) { Empty(); Type = GV_INT64; Value.Int64 = i; return *this; } LVariant &LVariant::operator =(uint64_t i) { Empty(); Type = GV_INT64; Value.Int64 = i; return *this; } LVariant &LVariant::operator =(double i) { Empty(); Type = GV_DOUBLE; Value.Dbl = i; // if (Dirty) *Dirty = true; return *this; } LVariant &LVariant::operator =(const char *s) { Empty(); if (s) { Type = GV_STRING; Value.String = NewStr(s); } return *this; } LVariant &LVariant::operator =(const char16 *s) { Empty(); if (s) { Type = GV_WSTRING; Value.WString = NewStrW(s); } // if (Dirty) *Dirty = true; return *this; } LVariant &LVariant::operator =(void *p) { Empty(); if (p) { Type = GV_VOID_PTR; Value.Ptr = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LDom *p) { Empty(); if (p) { Type = GV_DOM; Value.Dom = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LView *p) { Empty(); if (p) { Type = GV_GVIEW; Value.View = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LMouse *p) { Empty(); if (p) { Type = GV_LMOUSE; Value.Mouse = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LKey *p) { Empty(); if (p) { Type = GV_LKEY; Value.Key = p; } return *this; } LVariant &LVariant::operator =(LStream *s) { Empty(); if (s) { Type = GV_STREAM; Value.Stream.Ptr = s; Value.Stream.Own = false; } return *this; } LVariant &LVariant::operator =(LVariant const &i) { if (&i == this) return *this; Empty(); Type = i.Type; switch (Type) { case GV_NULL: { break; } case GV_INT32: { Value.Int = i.Value.Int; break; } case GV_BOOL: { Value.Bool = i.Value.Bool; break; } case GV_INT64: { Value.Int64 = i.Value.Int64; break; } case GV_DOUBLE: { Value.Dbl = i.Value.Dbl; break; } case GV_STRING: { Value.String = NewStr(((LVariant&)i).Str()); break; } case GV_WSTRING: { Value.WString = NewStrW(i.Value.WString); break; } case GV_BINARY: { SetBinary(i.Value.Binary.Length, i.Value.Binary.Data); break; } case GV_LIST: { SetList(i.Value.Lst); break; } case GV_DOM: { Value.Dom = i.Value.Dom; break; } case GV_VOID_PTR: case GV_GVIEW: case GV_LMOUSE: case GV_LKEY: { Value.Ptr = i.Value.Ptr; break; } case GV_DATETIME: { if (i.Value.Date) { Value.Date = new LDateTime; if (Value.Date) { *Value.Date = *i.Value.Date; } } break; } case GV_HASHTABLE: { if ((Value.Hash = new LHash)) { if (i.Value.Hash) { // const char *k; // for (LVariant *var = i.Value.Hash->First(&k); var; var = i.Value.Hash->Next(&k)) for (auto it : *i.Value.Hash) { Value.Hash->Add(it.key, new LVariant(*it.value)); } } } break; } case GV_CUSTOM: { Value.Custom.Data = i.Value.Custom.Data; Value.Custom.Dom = i.Value.Custom.Dom; break; } case GV_LSURFACE: { Value.Surface = i.Value.Surface; if (Value.Surface.Own && Value.Surface.Ptr) Value.Surface.Ptr->IncRef(); break; } /* case GV_GFILE: { Value.File = i.Value.File; if (Value.File.Own && Value.File.Ptr) Value.File.Ptr->AddRef(); break; } */ case GV_STREAM: { Value.Stream.Ptr = i.Value.Stream.Ptr; Value.Stream.Own = false; break; } default: { printf("%s:%i - Unknown variant type '%i'\n", _FL, Type); LAssert(0); break; } } // if (Dirty) *Dirty = true; return *this; } bool LVariant::SetDomRef(LDom *obj, char *name) { Empty(); Type = GV_DOMREF; Value.DomRef.Dom = obj; Value.DomRef.Name = NewStr(name); return Value.DomRef.Name != 0; } bool LVariant::SetBinary(ssize_t Len, void *Data, bool Own) { bool Status = false; Empty(); Type = GV_BINARY; Value.Binary.Length = Len; if (Own) { Value.Binary.Data = Data; Status = true; } else { if ((Value.Binary.Data = new uchar[Value.Binary.Length])) { if (Data) memcpy(Value.Binary.Data, Data, Value.Binary.Length); else memset(Value.Binary.Data, 0, Value.Binary.Length); Status = true; } } return Status; } List *LVariant::SetList(List *Lst) { Empty(); Type = GV_LIST; if ((Value.Lst = new List) && Lst) { for (auto s: *Lst) { LVariant *New = new LVariant; if (New) { *New = *s; Value.Lst->Insert(New); } } } return Value.Lst; } bool LVariant::SetHashTable(LHash *Table, bool Copy) { Empty(); Type = GV_HASHTABLE; if (Copy && Table) { if ((Value.Hash = new LHash)) { // const char *k; // for (LVariant *p = Table->First(&k); p; p = Table->Next(&k)) for (auto i : *Table) { Value.Hash->Add(i.key, i.value); } } } else { Value.Hash = Table ? Table : new LHash; } return Value.Hash != 0; } bool LVariant::SetSurface(class LSurface *Ptr, bool Own) { Empty(); if (!Ptr) return false; Type = GV_LSURFACE; Value.Surface.Ptr = Ptr; if ((Value.Surface.Own = Own)) Value.Surface.Ptr->IncRef(); return true; } bool LVariant::SetStream(class LStreamI *Ptr, bool Own) { Empty(); if (!Ptr) return false; Type = GV_STREAM; Value.Stream.Ptr = Ptr; Value.Stream.Own = Own; return true; } bool LVariant::OwnStr(char *s) { Empty(); if (!s) return false; Type = GV_STRING; Value.String = s; return true; } bool LVariant::OwnStr(char16 *w) { Empty(); if (!w) return false; Type = GV_WSTRING; Value.WString = w; return true; } char *LVariant::ReleaseStr() { char *Ret = Str(); if (Ret) { Value.String = 0; Type = GV_NULL; } return Ret; } LString LVariant::LStr() { return Str(); } char *LVariant::Str() { if (Type == GV_STRING) return Value.String; if (Type == GV_WSTRING) { char *u = WideToUtf8(Value.WString); DeleteArray(Value.WString); Type = GV_STRING; return Value.String = u; } return 0; } char16 *LVariant::ReleaseWStr() { char16 *Ret = WStr(); if (Ret) { Value.WString = 0; Type = GV_NULL; } return Ret; } char16 *LVariant::WStr() { if (Type == GV_WSTRING) return Value.WString; if (Type == GV_STRING) { char16 *w = Utf8ToWide(Value.String); DeleteArray(Value.String); Type = GV_WSTRING; return Value.WString = w; } return 0; } void LVariant::Empty() { switch (Type) { default: break; case GV_CUSTOM: { Value.Custom.Data = 0; Value.Custom.Dom = 0; break; } case GV_DOMREF: { DeleteArray(Value.DomRef.Name); Value.DomRef.Dom = 0; break; } case GV_STRING: { DeleteArray(Value.String); break; } case GV_WSTRING: { DeleteArray(Value.WString); break; } case GV_BINARY: { char *d = (char*) Value.Binary.Data; DeleteArray(d); Value.Binary.Data = 0; break; } case GV_DATETIME: { DeleteObj(Value.Date); break; } case GV_LIST: { if (Value.Lst) { Value.Lst->DeleteObjects(); DeleteObj(Value.Lst); } break; } case GV_HASHTABLE: { if (Value.Hash) { // for (LVariant *v = (LVariant*) Value.Hash->First(); v; v = (LVariant*) Value.Hash->Next()) for (auto i : *Value.Hash) { DeleteObj(i.value); } DeleteObj(Value.Hash); } break; } case GV_LSURFACE: { if (Value.Surface.Own && Value.Surface.Ptr) { Value.Surface.Ptr->DecRef(); Value.Surface.Ptr = NULL; } break; } /* case GV_GFILE: { if (Value.File.Ptr && Value.File.Own) { Value.File.Ptr->DecRef(); Value.File.Ptr = NULL; } break; } */ case GV_STREAM: { if (Value.Stream.Ptr) { if (Value.Stream.Own) delete Value.Stream.Ptr; Value.Stream.Ptr = NULL; } break; } } Type = GV_NULL; ZeroObj(Value); } int64 LVariant::Length() { switch (Type) { case GV_INT32: return sizeof(Value.Int); case GV_INT64: return sizeof(Value.Int64); case GV_BOOL: return sizeof(Value.Bool); case GV_DOUBLE: return sizeof(Value.Dbl); case GV_STRING: return Value.String ? strlen(Value.String) : 0; case GV_BINARY: return Value.Binary.Length; case GV_LIST: { int64 Sz = 0; if (Value.Lst) { for (auto v : *Value.Lst) Sz += v->Length(); } return Sz; } case GV_DOM: { LVariant v; if (Value.Dom) Value.Dom->GetValue("length", v); return v.CastInt32(); } case GV_DOMREF: break; case GV_VOID_PTR: return sizeof(Value.Ptr); case GV_DATETIME: return sizeof(*Value.Date); case GV_HASHTABLE: { int64 Sz = 0; if (Value.Hash) { // for (LVariant *v=Value.Hash->First(); v; v=Value.Hash->Next()) for (auto i : *Value.Hash) Sz += i.value->Length(); } return Sz; } case GV_OPERATOR: return sizeof(Value.Op); case GV_CUSTOM: break; case GV_WSTRING: return Value.WString ? StrlenW(Value.WString) * sizeof(char16) : 0; case GV_LSURFACE: { int64 Sz = 0; if (Value.Surface.Ptr) { LRect r = Value.Surface.Ptr->Bounds(); int Bytes = Value.Surface.Ptr->GetBits() >> 3; Sz = r.X() * r.Y() * Bytes; } return Sz; } case GV_GVIEW: return sizeof(LView); case GV_LMOUSE: return sizeof(LMouse); case GV_LKEY: return sizeof(LKey); case GV_STREAM: return Value.Stream.Ptr->GetSize(); default: break; } return 0; } bool LVariant::IsInt() { return Type == GV_INT32 || Type == GV_INT64; } bool LVariant::IsBool() { return Type == GV_BOOL; } bool LVariant::IsDouble() { return Type == GV_DOUBLE; } bool LVariant::IsString() { return Type == GV_STRING; } bool LVariant::IsBinary() { return Type == GV_BINARY; } bool LVariant::IsNull() { return Type == GV_NULL; } #define IsList() (Type == GV_LIST && Value.Lst) LVariant &LVariant::Cast(LVariantType NewType) { if (NewType != Type) { switch (NewType) { default: { // No conversion possible break; } case GV_INT32: { *this = (int)CastInt32(); break; } case GV_INT64: { *this = (int64_t) CastInt64(); break; } case GV_BOOL: { if (Type == GV_DOUBLE) { *this = Value.Dbl != 0.0; } else { *this = CastInt32() != 0; } break; } case GV_DOUBLE: { *this = CastDouble(); break; } case GV_STRING: { *this = CastString(); break; } case GV_DATETIME: { switch (Type) { case GV_STRING: { // String -> LDateTime LDateTime *Dt = new LDateTime; if (Dt) { Dt->Set(Value.String); Empty(); Value.Date = Dt; Type = NewType; } break; } case GV_INT64: { // Int64 (system date) -> LDateTime LDateTime *Dt = new LDateTime; if (Dt) { Dt->Set((uint64_t)Value.Int64); Empty(); Value.Date = Dt; Type = NewType; } break; } default: { // No conversion available break; } } break; } } } return *this; } void *LVariant::CastVoidPtr() const { switch (Type) { default: break; case GV_STRING: return Value.String; case GV_BINARY: return Value.Binary.Data; case GV_LIST: return Value.Lst; case GV_DOM: return Value.Dom; case GV_DOMREF: return Value.DomRef.Dom; case GV_VOID_PTR: return Value.Ptr; case GV_DATETIME: return Value.Date; case GV_HASHTABLE: return Value.Hash; case GV_CUSTOM: return Value.Custom.Data; case GV_WSTRING: return Value.WString; case GV_LSURFACE: return Value.Surface.Ptr; case GV_GVIEW: return Value.View; case GV_LMOUSE: return Value.Mouse; case GV_LKEY: return Value.Key; } return 0; } LDom *LVariant::CastDom() const { switch (Type) { default: break; case GV_DOM: return Value.Dom; case GV_DOMREF: return Value.DomRef.Dom; case GV_STREAM: return dynamic_cast(Value.Stream.Ptr); case GV_LSURFACE: return Value.Surface.Ptr; case GV_CUSTOM: return Value.Custom.Dom; } return NULL; } bool LVariant::CastBool() const { switch (Type) { default: LAssert(0); break; case GV_NULL: return false; case GV_INT32: return Value.Int != 0; case GV_INT64: return Value.Int64 != 0; case GV_BOOL: return Value.Bool; case GV_DOUBLE: return Value.Dbl != 0.0; case GV_BINARY: return Value.Binary.Data != NULL; case GV_LIST: return Value.Lst != NULL; case GV_DOM: return Value.Dom != NULL; case GV_DOMREF: return Value.DomRef.Dom != NULL; case GV_VOID_PTR: return Value.Ptr != NULL; case GV_GVIEW: return Value.View != NULL; case GV_LMOUSE: return Value.Mouse != NULL; case GV_LKEY: return Value.Key != NULL; case GV_DATETIME: return Value.Date != NULL; case GV_HASHTABLE: return Value.Hash != NULL; case GV_OPERATOR: return Value.Op != OpNull; case GV_CUSTOM: return Value.Custom.Dom != 0 && Value.Custom.Data != 0; /* case GV_GFILE: return Value.File.Ptr != NULL; */ case GV_STREAM: return Value.Stream.Ptr != NULL; // As far as I understand this is the behavour in Python, which I'm using for // a reference to what the "correct" thing to do here is. Basically it's treating // the string like a pointer instead of a value. If the pointer is valid the // conversion to bool return true, and false if it's not a valid pointer. This // means things like if (!StrinLVariant) evaluate correctly in the scripting engine // but it means that if you want to evaluate the value of the varient you should // use CastInt32 instead. case GV_STRING: return ValidStr(Value.String); case GV_WSTRING: return ValidStrW(Value.WString); } return false; } double LVariant::CastDouble() const { switch (Type) { default: break; case GV_BOOL: return Value.Bool ? 1.0 : 0.0; case GV_DOUBLE: return Value.Dbl; case GV_INT32: return (double)Value.Int; case GV_INT64: return (double)Value.Int64; case GV_STRING: return Value.String ? atof(Value.String) : 0; case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastDouble(); } } break; } } return 0; } int32 LVariant::CastInt32() const { switch (Type) { default: break; case GV_BOOL: return (int32)Value.Bool; case GV_DOUBLE: return (int32)Value.Dbl; case GV_INT32: return Value.Int; case GV_INT64: return (int32)Value.Int64; case GV_STRING: if (!Value.String) return 0; if (IsAlpha(Value.String[0])) return !Stricmp(Value.String, "true"); else if (Value.String[0] == '0' && tolower(Value.String[1]) == 'x') return static_cast(Atoi(Value.String, 16)); return atoi(Value.String); case GV_DOM: return Value.Dom != 0; case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastInt32(); } } break; } case GV_LIST: return Value.Lst != NULL; case GV_HASHTABLE: return Value.Hash != NULL; case GV_LSURFACE: return Value.Surface.Ptr != NULL; case GV_GVIEW: return Value.View != NULL; case GV_LMOUSE: return Value.Mouse != NULL; case GV_LKEY: return Value.Key != NULL; case GV_STREAM: return Value.Stream.Ptr != NULL; } return 0; } int64 LVariant::CastInt64() const { switch (Type) { default: break; case GV_BOOL: return (int64)Value.Bool; case GV_DOUBLE: return (int64)Value.Dbl; case GV_INT32: return Value.Int; case GV_INT64: return Value.Int64; case GV_STRING: { if (!Value.String) return 0; if (IsAlpha(Value.String[0])) return !Stricmp(Value.String, "true"); else if (Value.String[0] == '0' && tolower(Value.String[1]) == 'x') return Atoi(Value.String, 16); return Atoi(Value.String); } case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastInt64(); } } break; } } return 0; } char *LVariant::CastString() { char i[40]; switch (Type) { case GV_LIST: { LStringPipe p(256); List::I it = Value.Lst->begin(); bool First = true; p.Print("{"); for (LVariant *v = *it; v; v = *++it) { if (v->Type == GV_STRING || v->Type == GV_WSTRING) p.Print("%s\"%s\"", First ? "" : ", ", v->CastString()); else p.Print("%s%s", First ? "" : ", ", v->CastString()); First = false; } p.Print("}"); OwnStr(p.NewStr()); return Str(); break; } case GV_HASHTABLE: { LStringPipe p(256); p.Print("{"); bool First = true; // const char *k; // for (LVariant *v = Value.Hash->First(&k); v; v = Value.Hash->Next(&k)) for (auto i : *Value.Hash) { p.Print("%s%s = %s", First ? "" : ", ", i.key, i.value->CastString()); First = false; } p.Print("}"); OwnStr(p.NewStr()); return Str(); break; } case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastString(); } } break; } case GV_INT32: { sprintf_s(i, sizeof(i), "%i", Value.Int); *this = i; return Str(); } case GV_DOUBLE: { sprintf_s(i, sizeof(i), "%f", Value.Dbl); *this = i; return Str(); } case GV_BOOL: { sprintf_s(i, sizeof(i), "%i", Value.Bool); *this = i; return Str(); } case GV_INT64: { sprintf_s(i, sizeof(i), LPrintfInt64, Value.Int64); *this = i; return Str(); } case GV_STRING: case GV_WSTRING: { return Str(); } case GV_DATETIME: { if (Value.Date) { char s[64]; Value.Date->Get(s, sizeof(s)); *this = s; return Str(); } break; } case GV_DOM: { sprintf_s(i, sizeof(i), "dom:%p", Value.Dom); *this = i; break; } default: { break; } } return 0; } ///////////////////////////////////////////////////////////////////////////////// LDom *LDom::ResolveObject(const char *Var, LString &Name, LString &Array) { LDom *Object = this; try { // Tokenize the string LString::Array t; for (auto *s = Var; s && *s; ) { const char *e = s; while (*e && *e != '.') { if (*e == '[') { e++; while (*e && *e != ']') { if (*e == '\"' || *e == '\'') { char d = *e++; while (*e && *e != d) e++; if (*e == d) e++; } else e++; } if (*e == ']') e++; } else e++; } LString part = LString(s, e - s).Strip(); if (part.Length() > 0) t.New() = part; // Store non-empty part s = *e ? e + 1 : e; } // Process elements for (int i=0; iGetVariant(Obj, v, Index)) { if (v.Type == GV_LIST) { int N = atoi(Index); LVariant *Element = v.Value.Lst->ItemAt(N); if (Element && Element->Type == GV_DOM) { Object = Element->Value.Dom; } else { return NULL; } } else if (v.Type == GV_DOM) { Object = v.Value.Dom; } else { return NULL; } } else { return NULL; } } else { if (Object->GetVariant(Obj, v) && v.Type == GV_DOM) { Object = v.Value.Dom; } else { return NULL; } } } } } catch (...) { LgiTrace("LDom::ResolveObject crashed: '%s'\n", Var); return NULL; } return Object; } struct LDomPropMap { LHashTbl, LDomProperty> ToProp; LHashTbl, const char *> ToString; LDomPropMap() { #undef _ #define _(symbol, txt) Define(txt, symbol); #include "lgi/common/DomFields.h" #undef _ } void Define(const char *s, LDomProperty p) { if (!s) return; #if defined(_DEBUG) // Check for duplicates. auto existing_prop = ToProp.Find(s); LAssert(existing_prop == ObjNone); auto existing_str = ToString.Find(p); LAssert(existing_str == NULL); #endif ToProp.Add(s, p); ToString.Add(p, s); } } DomPropMap; LDomProperty LStringToDomProp(const char *Str) { return DomPropMap.ToProp.Find(Str); } const char *LDomPropToString(LDomProperty Prop) { return DomPropMap.ToString.Find(Prop); } bool LDom::GetValue(const char *Var, LVariant &Value) { if (!Var) return false; if (!_OnAccess(true)) { LgiTrace("%s:%i - Locking error\n", _FL); LAssert(0); return false; } bool Status = false; LString Name, Arr; LDom *Object = ResolveObject(Var, Name, Arr); if (Object) { if (Name.IsEmpty()) LgiTrace("%s:%i - Warning name parse failed for '%s'\n", _FL, Var); else Status = Object->GetVariant(Name, Value, Arr.IsEmpty() ? NULL : Arr.Get()); } _OnAccess(false); return Status; } bool LDom::SetValue(const char *Var, LVariant &Value) { bool Status = false; if (Var) { // LMutex *Sem = dynamic_cast(this); if (_OnAccess(true)) { LString Name, Arr; LDom *Object = ResolveObject(Var, Name, Arr); if (Object) { if (Name.IsEmpty()) LgiTrace("%s:%i - Warning name parse failed for '%s'\n", _FL, Var); else Status = Object->SetVariant(Name, Value, Arr.IsEmpty() ? NULL : Arr.Get()); } _OnAccess(false); } else { LgiTrace("%s:%i - Locking error\n", _FL); LAssert(0); } } return Status; } bool LVariant::Add(LVariant *v, int Where) { if (!v) { LAssert(!"No value to insert."); return false; } if (Type == GV_NULL) SetList(); if (Type != GV_LIST) { LAssert(!"Not a list variant"); return false; } return Value.Lst->Insert(v, Where); } LString LVariant::ToString() { LString s; switch (Type) { case GV_NULL: s = "NULL"; break; case GV_INT32: s.Printf("(int)%i", Value.Int); break; case GV_INT64: s.Printf("(int64)" LPrintfInt64, Value.Int64); break; case GV_BOOL: s.Printf("(bool)%s", Value.Bool ? "true" : "false"); break; case GV_DOUBLE: s.Printf("(double)%f", Value.Dbl); break; case GV_STRING: s.Printf("(string)\"%s\"", Value.String); break; case GV_BINARY: s.Printf("(binary[%i])%p", Value.Binary.Length, Value.Binary.Data); break; case GV_LIST: s.Printf("(list[%i])%p", Value.Lst?Value.Lst->Length():0, Value.Lst); break; case GV_DOM: s.Printf("(dom)%p", Value.Dom); break; case GV_DOMREF: s.Printf("(dom)%p.%s", Value.DomRef.Dom, Value.DomRef.Name); break; case GV_VOID_PTR: s.Printf("(void*)%p", Value.Ptr); break; case GV_DATETIME: { char dt[64]; Value.Date->Get(dt, sizeof(dt)); s.Printf("(datetime)%s", dt); break; } case GV_HASHTABLE: s.Printf("(hashtbl)%p", Value.Hash); break; case GV_OPERATOR: s.Printf("(operator)%s", OperatorToString(Value.Op)); break; case GV_CUSTOM: s.Printf("(custom.%s)%p", Value.Custom.Dom->GetName(), Value.Custom.Data); break; case GV_WSTRING: s.Printf("(wstring)\"%S\"", Value.WString); break; case GV_LSURFACE: s.Printf("(gsurface)%p", Value.Surface.Ptr); break; case GV_GVIEW: s.Printf("(gview)%p", Value.View); break; case GV_LMOUSE: s.Printf("(gmouse)%p", Value.Mouse); break; case GV_LKEY: s.Printf("(gkey)%p", Value.Key); break; case GV_STREAM: s.Printf("(stream)%p", Value.Stream.Ptr); break; default: s = "(unknown)NULL"; break; } return s; } ///////////////////////////////////////////////////////////////////////////////////////////////////// LCustomType::LCustomType(const char *name, int pack) : FldMap(0, -1) { Name = name; Pack = 1; Size = 0; } LCustomType::LCustomType(const char16 *name, int pack) : FldMap(0, -1) { Name = name; Pack = 1; Size = 0; } LCustomType::~LCustomType() { Flds.DeleteObjects(); Methods.DeleteObjects(); } size_t LCustomType::Sizeof() { return (size_t)PadSize(); } ssize_t LCustomType::PadSize() { if (Pack > 1) { // Bump size to the pack boundary... int Remain = Size % Pack; if (Remain) return Size + Pack - Remain; } return Size; } int LCustomType::IndexOf(const char *Field) { return FldMap.Find(Field); } int LCustomType::AddressOf(const char *Field) { if (!Field) return -1; for (unsigned i=0; iName, Field)) return (int)i; } return -1; } bool LCustomType::DefineField(const char *Name, LCustomType *Type, int ArrayLen) { if (ArrayLen < 1) { LAssert(!"Can't have zero size field."); return false; } if (Name == NULL || Type == NULL) { LAssert(!"Invalid parameter."); return false; } if (FldMap.Find(Name) >= 0) { LAssert(!"Field already exists."); return false; } FldMap.Add(Name, (int)Flds.Length()); CustomField *Def; Flds.Add(Def = new CustomField); Size = PadSize(); Def->Offset = Size; Def->Name = Name; Def->Type = GV_CUSTOM; Def->Bytes = Type->Sizeof(); Def->ArrayLen = ArrayLen; Size += Def->Bytes * ArrayLen; return true; } bool LCustomType::DefineField(const char *Name, LVariantType Type, int Bytes, int ArrayLen) { if (ArrayLen < 1) { LAssert(!"Can't have zero size field."); return false; } if (Name == NULL) { LAssert(!"No field name."); return false; } if (FldMap.Find(Name) >= 0) { LAssert(!"Field already exists."); return false; } FldMap.Add(Name, (int)Flds.Length()); CustomField *Def; Flds.Add(Def = new CustomField); Size = PadSize(); Def->Offset = Size; Def->Name = Name; Def->Type = Type; Def->Bytes = Bytes; Def->ArrayLen = ArrayLen; Size += Bytes * ArrayLen; return true; } LCustomType::Method *LCustomType::GetMethod(const char *Name) { return MethodMap.Find(Name); } LCustomType::Method *LCustomType::DefineMethod(const char *Name, LArray &Params, size_t Address) { Method *m = MethodMap.Find(Name); if (m) { LAssert(!"Method already defined."); return NULL; } Methods.Add(m = new Method); m->Name = Name; m->Params = Params; m->Address = Address; MethodMap.Add(Name, m); return m; } bool LCustomType::CustomField::GetVariant(const char *Field, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Field); switch (p) { case ObjName: // Type: String Value = Name; break; case ObjLength: // Type: Int32 Value = Bytes; break; default: return false; } return true; } ssize_t LCustomType::CustomField::Sizeof() { switch (Type) { case GV_INT32: return sizeof(int32); case GV_INT64: return sizeof(int64); case GV_BOOL: return sizeof(bool); case GV_DOUBLE: return sizeof(double); case GV_STRING: return sizeof(char); case GV_DATETIME: return sizeof(LDateTime); case GV_HASHTABLE: return sizeof(LVariant::LHash); case GV_OPERATOR: return sizeof(LOperator); case GV_LMOUSE: return sizeof(LMouse); case GV_LKEY: return sizeof(LKey); case GV_CUSTOM: return Nested->Sizeof(); default: LAssert(!"Unknown type."); break; } return 0; } bool LCustomType::Get(int Index, LVariant &Out, uint8_t *This, int ArrayIndex) { if (Index < 0 || Index >= Flds.Length() || !This) { LAssert(!"Invalid parameter error."); return false; } CustomField *Def = Flds[Index]; if (ArrayIndex < 0 || ArrayIndex >= Def->ArrayLen) { LAssert(!"Array out of bounds."); return false; } uint8_t *Ptr = This + Def->Offset; Out.Empty(); switch (Def->Type) { case GV_STRING: { int Len; for (Len = 0; Ptr[Len] && Len < Def->ArrayLen-1; Len++) ; Out.OwnStr(NewStr((char*)Ptr, Len)); break; } case GV_WSTRING: { char16 *p = (char16*)Ptr; int Len; for (Len = 0; p[Len] && Len < Def->ArrayLen-1; Len++) ; Out.OwnStr(NewStrW(p, Len)); break; } case GV_INT32: case GV_INT64: { switch (Def->Bytes) { case 1: { Out.Value.Int = Ptr[ArrayIndex]; Out.Type = GV_INT32; break; } case 2: { Out.Value.Int = ((uint16*)Ptr)[ArrayIndex]; Out.Type = GV_INT32; break; } case 4: { Out.Value.Int = ((uint32_t*)Ptr)[ArrayIndex]; Out.Type = GV_INT32; break; } case 8: { Out.Value.Int64 = ((uint64*)Ptr)[ArrayIndex]; Out.Type = GV_INT64; break; } default: { LAssert(!"Unknown integer size."); return false; } } break; } case GV_MAX: { Out = *((LVariant*)Ptr); break; } default: { LAssert(!"Impl this type."); return false; } } return true; } bool LCustomType::Set(int Index, LVariant &In, uint8_t *This, int ArrayIndex) { if (Index < 0 || Index >= Flds.Length() || !This) { LAssert(!"Invalid parameter error."); return false; } CustomField *Def = Flds[Index]; uint8_t *Ptr = This + Def->Offset; if (ArrayIndex < 0 || ArrayIndex >= Def->ArrayLen) { LAssert(!"Array out of bounds."); return false; } switch (Def->Type) { case GV_STRING: { char *s = In.Str(); if (!s) { *Ptr = 0; break; } if (Def->Bytes == 1) { // Straight up string copy... if (s) strcpy_s((char*)Ptr, Def->ArrayLen, s); else *Ptr = 0; } else if (Def->Bytes == sizeof(char16)) { // utf8 -> wide conversion... const void *In = Ptr; ssize_t Len = strlen(s); ssize_t Ch = LBufConvertCp(Ptr, LGI_WideCharset, Def->ArrayLen-1, In, "utf-8", Len); if (Ch >= 0) { // Null terminate Ptr[Ch] = 0; } else { LAssert(!"LBufConvertCp failed."); return false; } } break; } case GV_WSTRING: { char16 *p = (char16*)Ptr; char16 *w = In.WStr(); if (!w) { *p = 0; break; } if (Def->Bytes == sizeof(char16)) { // Straight string copy... Strcpy(p, Def->ArrayLen, w); } else { // Conversion to utf-8 const void *In = Ptr; ssize_t Len = StrlenW(w) * sizeof(char16); ssize_t Ch = LBufConvertCp(Ptr, "utf-8", Def->ArrayLen-sizeof(char16), In, LGI_WideCharset, Len); if (Ch >= 0) { // Null terminate p[Ch/sizeof(char16)] = 0; } else { LAssert(!"LBufConvertCp failed."); return false; } } break; } case GV_INT32: case GV_INT64: { switch (Def->Bytes) { case 1: { Ptr[ArrayIndex] = In.CastInt32(); break; } case 2: { ((uint16*)Ptr)[ArrayIndex] = In.CastInt32(); break; } case 4: { ((uint32_t*)Ptr)[ArrayIndex] = In.CastInt32(); break; } case 8: { ((uint64*)Ptr)[ArrayIndex] = In.CastInt64(); break; } default: { LAssert(!"Unknown integer size."); return false; } } break; } case GV_MAX: { *((LVariant*)Ptr) = In; break; } default: LAssert(!"Impl this type."); break; } return true; } bool LCustomType::GetVariant(const char *Field, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Field); switch (p) { case ObjName: // Type: String { Value = Name; return true; } case ObjType: // Type: String { Value = "LCustomType"; return true; } case ObjLength: // Type: Int32 { Value = (int)Sizeof(); return true; } case ObjField: // Type: CustomField[] { if (Array) { int Index = atoi(Array); if (Index >= 0 && Index < Flds.Length()) { Value = (LDom*)&Flds[Index]; return true; } } else { Value = (int)Flds.Length(); break; } break; } default: break; } LAssert(0); return false; } bool LCustomType::SetVariant(const char *Name, LVariant &Value, const char *Array) { LAssert(0); return false; } bool LCustomType::CallMethod(const char *MethodName, LScriptArguments &Args) { if (!MethodName) return false; if (!_stricmp(MethodName, "New")) { Args.GetReturn()->Empty(); Args.GetReturn()->Type = GV_CUSTOM; Args.GetReturn()->Value.Custom.Dom = this; Args.GetReturn()->Value.Custom.Data = new uint8_t[Sizeof()]; return true; } if (!_stricmp(MethodName, "Delete")) // Type: (Object) { for (unsigned i=0; iType == GV_CUSTOM) { DeleteArray(v->Value.Custom.Data); v->Empty(); } } return true; } LAssert(0); return false; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// LStream LScriptArguments::NullConsole; LScriptArguments::LScriptArguments(LVirtualMachineI *vm, LVariant *ret, LStream *console) { Vm = vm; if (ret) Return = ret; else Return = LocalReturn = new LVariant; if (console) Console = console; else Console = &NullConsole; } LScriptArguments::~LScriptArguments() { DeleteObjects(); DeleteObj(LocalReturn); } const char *LScriptArguments::StringAt(size_t i) { return IdxCheck(i) ? (*this)[i]->Str() : NULL; } int32_t LScriptArguments::Int32At(size_t i, int32_t Default) { return IdxCheck(i) ? (*this)[i]->CastInt32() : Default; } int64_t LScriptArguments::Int64At(size_t i, int64_t Default) { return IdxCheck(i) ? (*this)[i]->CastInt64() : Default; } double LScriptArguments::DoubleAt(size_t i, double Default) { return IdxCheck(i) ? (*this)[i]->CastDouble() : Default; } -bool LScriptArguments::Throw(const char *File, int Line, const char *Msg, ...) +bool LScriptArguments::Throw(const char *file, int line, const char *Msg, ...) { if (!Vm) return false; + File = file; + Line = line; + va_list Arg; va_start(Arg, Msg); - - LString s; - s.Printf(Arg, Msg); - + ExceptionMsg.Printf(Arg, Msg); va_end(Arg); - Vm->OnException(File, Line, -1, s); + Vm->OnException(File, Line, -1, ExceptionMsg); return true; } diff --git a/src/win/Lgi/Window.cpp b/src/win/Lgi/Window.cpp --- a/src/win/Lgi/Window.cpp +++ b/src/win/Lgi/Window.cpp @@ -1,1416 +1,1423 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Edit.h" #include "lgi/common/Popup.h" #include "lgi/common/ToolBar.h" #include "lgi/common/Panel.h" #include "lgi/common/Variant.h" #include "lgi/common/Button.h" #include "lgi/common/Notifications.h" #include "lgi/common/CssTools.h" #include "lgi/common/Menu.h" #include "ViewPriv.h" #define DEBUG_WINDOW_PLACEMENT 0 #define DEBUG_HANDLE_VIEW_KEY 0 #define DEBUG_HANDLE_VIEW_MOUSE 0 #define DEBUG_SERIALIZE_STATE 0 #define DEBUG_SETFOCUS 0 extern bool In_SetWindowPos; typedef UINT (WINAPI *ProcGetDpiForWindow)(_In_ HWND hwnd); typedef UINT (WINAPI *ProcGetDpiForSystem)(VOID); LLibrary User32("User32"); LPoint LGetDpiForWindow(HWND hwnd) { static bool init = false; static ProcGetDpiForWindow pGetDpiForWindow = NULL; static ProcGetDpiForSystem pGetDpiForSystem = NULL; if (!init) { init = true; pGetDpiForWindow = (ProcGetDpiForWindow) User32.GetAddress("GetDpiForWindow"); pGetDpiForSystem = (ProcGetDpiForSystem) User32.GetAddress("GetDpiForSystem"); } if (pGetDpiForWindow && pGetDpiForSystem) { auto Dpi = hwnd ? pGetDpiForWindow(hwnd) : pGetDpiForSystem(); return LPoint(Dpi, Dpi); } return LScreenDpi(); } /////////////////////////////////////////////////////////////////////////////////////////////// class HookInfo { public: int Flags; LView *Target; }; class LWindowPrivate { public: LArray Hooks; bool SnapToEdge; bool AlwaysOnTop; LWindowZoom Show; bool InCreate; LAutoPtr Wp; LPoint Dpi; int ShowCmd = SW_NORMAL; // Focus stuff LViewI *Focus; LWindowPrivate() { Focus = NULL; InCreate = true; Show = LZoomNormal; SnapToEdge = false; AlwaysOnTop = false; } ~LWindowPrivate() { } ssize_t GetHookIndex(LView *Target, bool Create = false) { for (int i=0; iTarget = Target; n->Flags = 0; return (ssize_t)Hooks.Length() - 1; } } return -1; } }; /////////////////////////////////////////////////////////////////////////////////////////////// LWindow::LWindow() : LView(0) { _Window = this; d = new LWindowPrivate; SetStyle(GetStyle() | WS_TILEDWINDOW | WS_CLIPCHILDREN); SetStyle(GetStyle() & ~WS_CHILD); SetExStyle(GetExStyle() | WS_EX_CONTROLPARENT); LWindowsClass *c = LWindowsClass::Create(GetClass()); if (c) c->Register(); Visible(false); _Lock = new LMutex("LWindow"); } LWindow::~LWindow() { if (LAppInst && LAppInst->AppWnd == this) LAppInst->AppWnd = NULL; if (Menu) { Menu->Detach(); DeleteObj(Menu); } DeleteObj(_Lock); DeleteObj(d); } int LWindow::WaitThread() { // No thread to wait on... return 0; } bool LWindow::SetTitleBar(bool ShowTitleBar) { if (ShowTitleBar) { SetStyle(GetStyle() | WS_TILEDWINDOW); } else { SetStyle(GetStyle() & ~WS_TILEDWINDOW); SetStyle(GetStyle() | WS_POPUP); } return true; } bool LWindow::SetIcon(const char *Icon) { return CreateClassW32(LAppInst->Name(), LoadIcon(LProcessInst(), (LPCWSTR)Icon)) != 0; } LViewI *LWindow::GetFocus() { return d->Focus; } static LAutoString DescribeView(LViewI *v) { if (!v) return LAutoString(NewStr("NULL")); char s[512]; int ch = 0; ::LArray p; for (LViewI *i = v; i; i = i->GetParent()) { p.Add(i); } for (auto n=MIN(3, (ssize_t)p.Length()-1); n>=0; n--) { v = p[n]; ch += sprintf_s(s + ch, sizeof(s) - ch, ">%s", v->GetClass()); } return LAutoString(NewStr(s)); } static bool HasParentPopup(LViewI *v) { for (; v; v = v->GetParent()) { if (dynamic_cast(v)) return true; } return false; } bool LWindow::SetWillFocus(bool f) { d->ShowCmd = f ? SW_NORMAL : SW_SHOWNOACTIVATE; return true; } void LWindow::SetFocus(LViewI *ctrl, FocusType type) { const char *TypeName = NULL; switch (type) { case GainFocus: TypeName = "Gain"; break; case LoseFocus: TypeName = "Lose"; break; case ViewDelete: TypeName = "Delete"; break; } switch (type) { case GainFocus: { LViewI *This = this; if (ctrl == This && d->Focus) { // The main LWindow is getting focus. // Check if we can re-focus the previous child focus... LView *v = d->Focus->GetGView(); if (v && !HasParentPopup(v)) { // We should never return focus to a popup, or it's child. if (!(v->WndFlags & GWF_FOCUS)) { // Yes, the child view doesn't think it has focus... // So re-focus it... if (v->Handle()) { // Non-virtual window... ::SetFocus(v->Handle()); } v->WndFlags |= GWF_FOCUS; v->OnFocus(true); v->Invalidate(); #if DEBUG_SETFOCUS LAutoString _set = DescribeView(ctrl); LAutoString _foc = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) refocusing: %s\n", _set.Get(), TypeName, _foc.Get()); #endif return; } } } // Check if the control already has focus if (d->Focus == ctrl) return; if (d->Focus) { LView *v = d->Focus->GetGView(); if (v) v->WndFlags &= ~GWF_FOCUS; d->Focus->OnFocus(false); d->Focus->Invalidate(); #if DEBUG_SETFOCUS LAutoString _foc = DescribeView(d->Focus); LgiTrace(".....defocus: %s\n", _foc.Get()); #endif } d->Focus = ctrl; if (d->Focus) { LView *v = d->Focus->GetGView(); if (v) v->WndFlags |= GWF_FOCUS; d->Focus->OnFocus(true); d->Focus->Invalidate(); #if DEBUG_SETFOCUS LAutoString _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) focusing\n", _set.Get(), TypeName); #endif } break; } case LoseFocus: { if (ctrl == d->Focus) { LView *v = d->Focus->GetGView(); if (v) { if (v->WndFlags & GWF_FOCUS) { // View thinks it has focus v->WndFlags &= ~GWF_FOCUS; d->Focus->OnFocus(false); // keep d->Focus pointer, as we want to be able to re-focus the child // view when we get focus again #if DEBUG_SETFOCUS LAutoString _ctrl = DescribeView(ctrl); LAutoString _foc = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) keep_focus: %s\n", _ctrl.Get(), TypeName, _foc.Get()); #endif } // else view doesn't think it has focus anyway... } else { // Non LView handler d->Focus->OnFocus(false); d->Focus->Invalidate(); d->Focus = NULL; } } else { /* LgiTrace("LWindow::SetFocus(%p.%s, %s) error on losefocus: %p(%s)\n", ctrl, ctrl ? ctrl->GetClass() : NULL, TypeName, d->Focus, d->Focus ? d->Focus->GetClass() : NULL); */ } break; } case ViewDelete: { if (ctrl == d->Focus) { #if DEBUG_SETFOCUS LgiTrace("LWindow::SetFocus(%p.%s, %s) delete_focus: %p(%s)\n", ctrl, ctrl ? ctrl->GetClass() : NULL, TypeName, d->Focus, d->Focus ? d->Focus->GetClass() : NULL); #endif d->Focus = NULL; } break; } } } bool LWindow::GetSnapToEdge() { return d->SnapToEdge; } void LWindow::SetSnapToEdge(bool b) { d->SnapToEdge = b; } bool LWindow::GetAlwaysOnTop() { return d->AlwaysOnTop; } void LWindow::SetAlwaysOnTop(bool b) { d->AlwaysOnTop = b; if (_View) SetWindowPos(_View, b ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); } void LWindow::Raise() { if (_View) { DWORD dwFGProcessId; DWORD dwFGThreadId = GetWindowThreadProcessId(_View, &dwFGProcessId); DWORD dwThisThreadId = GetCurrentThreadId(); AttachThreadInput(dwThisThreadId, dwFGThreadId, true); SetForegroundWindow(_View); BringWindowToTop(_View); if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p)\n", __FILE__, __LINE__, _View); } ::SetFocus(_View); AttachThreadInput(dwThisThreadId, dwFGThreadId, false); } } LWindowZoom LWindow::GetZoom() { if (IsZoomed(Handle())) { return LZoomMax; } else if (IsIconic(Handle())) { return LZoomMin; } return LZoomNormal; } void LWindow::SetZoom(LWindowZoom i) { if (_View && IsWindowVisible(_View)) { switch (i) { case LZoomMax: { ShowWindow(Handle(), SW_MAXIMIZE); break; } case LZoomMin: { ShowWindow(Handle(), SW_MINIMIZE); break; } case LZoomNormal: { if (!Visible()) { Visible(true); } if (IsIconic(Handle()) || IsZoomed(Handle())) { ShowWindow(Handle(), d->ShowCmd); } - LYield(); + // Process some messages: is this needed still? + MSG Msg = {0}; + while ( PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE) && + Msg.message != WM_QUIT) + { + TranslateMessage(&Msg); + DispatchMessage(&Msg); + } RECT r; GetWindowRect(Handle(), &r); if (r.left != Pos.x1 || r.top != Pos.y1) { int Shadow = WINDOWS_SHADOW_AMOUNT; SetWindowPos(Handle(), 0, Pos.x1, Pos.y1, Pos.X() + Shadow, Pos.Y() + Shadow, SWP_NOZORDER); } break; } } } d->Show = i; } bool LWindow::OnRequestClose(bool OsShuttingDown) { if (GetQuitOnClose()) { LCloseApp(); } return true; } bool LWindow::HandleViewMouse(LView *v, LMouse &m) { #if DEBUG_HANDLE_VIEW_MOUSE m.Trace("HandleViewMouse"); #endif for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LMouseEvents) { LView *t = d->Hooks[i].Target; if (!t->OnViewMouse(v, m)) { #if DEBUG_HANDLE_VIEW_MOUSE if (m.IsMove()) LgiTrace(" Hook %i of %i ate mouse event: '%s'\n", i, d->Hooks.Length(), d->Hooks[i].Target->GetClass()); #endif return false; } } } #if DEBUG_HANDLE_VIEW_MOUSE if (!m.IsMove()) LgiTrace(" Passing mouse event to '%s'\n", v->GetClass()); #endif return true; } bool LWindow::HandleViewKey(LView *v, LKey &k) { #if DEBUG_HANDLE_VIEW_KEY char msg[256]; sprintf_s(msg, sizeof(msg), "HandleViewKey, v=%s", v ? v->GetClass() : "NULL"); k.Trace(msg); #endif // Any window in a pop up always gets the key... LViewI *p; for (p = v->GetParent(); p; p = p->GetParent()) { if (dynamic_cast(p)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Popup %s handling key.\n", p->GetClass()); #endif return v->OnKey(k); } } // Allow any hooks to see the key... #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" d->Hooks.Length()=%i.\n", (int)d->Hooks.Length()); #endif for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LKeyEvents) { if (d->Hooks[i].Target->OnViewKey(v, k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Hook[%i] %s handling key.\n", i, d->Hooks[i].Target->GetClass()); #endif return true; } } } // Give the key to the focused window... if (d->Focus && d->Focus->OnKey(k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" d->Focus %s handling key.\n", d->Focus->GetClass()); #endif return true; } // Check default controls p = 0; if (k.c16 == VK_RETURN) { if (!_Default) p = _Default = FindControl(IDOK); else p = _Default; #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Using _Default ctrl (%s).\n", p ? p->GetClass() : "NULL"); #endif } else if (k.c16 == VK_ESCAPE) { p = FindControl(IDCANCEL); if (p) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Using IDCANCEL ctrl (%s).\n", p->GetClass()); #endif } } if (p && p->OnKey(k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Default control %s handled key.\n", p->GetClass()); #endif return true; } // Menu shortcut? if (Menu && Menu->OnKey(v, k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Menu handled key.\n"); #endif return true; } // Control shortcut? if (k.Down() && k.Alt() && k.c16 > ' ') { ShortcutMap Map; BuildShortcuts(Map); LViewI *c = Map.Find(ToUpper(k.c16)); if (c) { c->OnNotify(c, LNotifyActivate); return true; } } #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" No one handled key.\n"); #endif return false; } void LWindow::OnPaint(LSurface *pDC) { auto c = GetClient(); LCssTools Tools(this); Tools.PaintContent(pDC, c); } bool LWindow::Obscured() { RECT tRect; bool isObscured = false; if (GetWindowRect(_View, &tRect)) { RECT nRect; HWND walker = _View; while (walker = ::GetNextWindow(walker, GW_HWNDPREV)) { if (IsWindowVisible(walker)) { if ((::GetWindowRect(walker, &nRect))) { RECT iRect; IntersectRect(&iRect, &tRect, &nRect); if (iRect.bottom || iRect.top || iRect.left || iRect.right) { isObscured = true; break; } } } } } return isObscured; } bool LWindow::Visible() { return LView::Visible(); } void LWindow::Visible(bool v) { if (v) PourAll(); if (v) { SetStyle(GetStyle() | WS_VISIBLE); if (_View) { LWindowZoom z = d->Show; char *Cmd = 0; LAutoPtr Wp(new WINDOWPLACEMENT); if (Wp) { ZeroObj(*Wp.Get()); Wp->length = sizeof(*Wp); Wp->flags = 2; Wp->ptMaxPosition.x = -1; Wp->ptMaxPosition.y = -1; if (d->Show == LZoomMax) { Wp->showCmd = SW_MAXIMIZE; Cmd = "SW_MAXIMIZE"; } else if (d->Show == LZoomMin) { Wp->showCmd = SW_MINIMIZE; Cmd = "SW_MINIMIZE"; } else { Wp->showCmd = d->ShowCmd; Cmd = "SW_NORMAL"; } Wp->rcNormalPosition = Pos; #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - SetWindowPlacement, pos=%s, show=%i\n", __FILE__, __LINE__, Pos.GetStr(), Wp->showCmd); #endif SetWindowPlacement(_View, Wp); if (d->InCreate) d->Wp = Wp; } } } else { #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - Visible(%i)\n", __FILE__, __LINE__, v); #endif LView::Visible(v); } if (v) { OnZoom(d->Show); } } static bool IsAppWnd(HWND h) { if (!IsWindowVisible(h)) return false; auto flags = GetWindowLong(h, GWL_STYLE); if (flags & WS_POPUP) return false; return true; } bool LWindow::IsActive() { auto top = GetTopWindow(GetDesktopWindow()); while (top && !IsAppWnd(top)) top = ::GetWindow(top, GW_HWNDNEXT); return top == _View; } bool LWindow::SetActive() { if (!_View) return false; return SetWindowPos(_View, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE) != 0; } void LWindow::PourAll() { LRegion Client(GetClient()); LRegion Update; bool HasTools = false; { LRegion Tools; for (auto v: Children) { LView *k = dynamic_cast(v); if (k && k->_IsToolBar) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (HasTools) { // 2nd and later toolbars if (v->Pour(Tools)) { if (!v->Visible()) { v->Visible(true); } auto vpos = v->GetPos(); if (OldPos != vpos) { // position has changed update... v->Invalidate(); } // Has it increased the size of the toolbar area? auto b = Tools.Bound(); if (vpos.y2 >= b.y2) { LRect Bar = Client; Bar.y2 = vpos.y2; Client.Subtract(&Bar); // LgiTrace("IncreaseToolbar=%s\n", Bar.GetStr()); } Tools.Subtract(&vpos); Update.Subtract(&vpos); // LgiTrace("vpos=%s\n", vpos.GetStr()); } } else { // First toolbar if (v->Pour(Client)) { HasTools = true; if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { v->Invalidate(); } LRect Bar(v->GetPos()); Bar.x2 = GetClient().x2; Tools = Bar; Tools.Subtract(&v->GetPos()); Client.Subtract(&Bar); Update.Subtract(&Bar); } } } } } // LgiTrace("Client=%s\n", Client.Bound().GetStr()); for (auto v: Children) { LView *k = dynamic_cast(v); if (!(k && k->_IsToolBar)) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (v->Pour(Client)) { if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { // position has changed update... v->Invalidate(); } Client.Subtract(&v->GetPos()); Update.Subtract(&v->GetPos()); } else { // make the view not visible // v->Visible(FALSE); } } } for (int i=0; iMsg()) { case WM_DPICHANGED: { d->Dpi.x = HIWORD(Msg->A()); d->Dpi.y = LOWORD(Msg->A()); OnPosChange(); break; } case M_ASSERT_UI: { LAutoPtr Str((LString*)Msg->A()); extern void LAssertDlg(LString Msg, std::function Callback); if (Str) LAssertDlg(Str ? *Str : "Error: no msg.", NULL); break; } case M_SET_WINDOW_PLACEMENT: { /* Apparently if you use SetWindowPlacement inside the WM_CREATE handler, then the restored rect doesn't "stick", it gets stomped on by windows. So this code... RESETS it to be what we set earlier. Windows sucks. */ if (!d->Wp || !_View) break; LRect r = d->Wp->rcNormalPosition; if (!LView::Visible()) d->Wp->showCmd = SW_HIDE; #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - SetWindowPlacement, pos=%s, show=%i\n", __FILE__, __LINE__, r.GetStr(), d->Wp->showCmd); #endif SetWindowPlacement(_View, d->Wp); d->Wp.Reset(); break; } case WM_SYSCOLORCHANGE: { LColour::OnChange(); break; } case WM_WINDOWPOSCHANGING: { bool Icon = IsIconic(Handle()) != 0; bool Zoom = IsZoomed(Handle()) != 0; if (!Icon && (_Dialog || !Zoom)) { WINDOWPOS *Info = (LPWINDOWPOS) Msg->b; if (!Info) break; if (Info->flags == (SWP_NOSIZE | SWP_NOMOVE) && _Dialog) { // Info->flags |= SWP_NOZORDER; Info->hwndInsertAfter = _Dialog->Handle(); } if (GetMinimumSize().x && GetMinimumSize().x > Info->cx) { Info->cx = GetMinimumSize().x; } if (GetMinimumSize().y && GetMinimumSize().y > Info->cy) { Info->cy = GetMinimumSize().y; } /* This is broken on windows 10... windows get stuck on the edge of the desktop. RECT Rc; if (d->SnapToEdge && SystemParametersInfo(SPI_GETWORKAREA, 0, &Rc, SPIF_SENDCHANGE)) { LRect r = Rc; LRect p(Info->x, Info->y, Info->x + Info->cx - 1, Info->y + Info->cy - 1); if (r.Valid() && p.Valid()) { int Snap = 12; if (abs(p.x1 - r.x1) <= Snap) { // Snap left edge Info->x = r.x1; } else if (abs(p.x2 - r.x2) <= Snap) { // Snap right edge Info->x = r.x2 - Info->cx + 1; } if (abs(p.y1 - r.y1) <= Snap) { // Snap top edge Info->y = r.y1; } else if (abs(p.y2 - r.y2) <= Snap) { // Snap bottom edge Info->y = r.y2 - Info->cy + 1; } } } */ } break; } case WM_SIZE: { if (Visible()) { LWindowZoom z = d->Show; switch (Msg->a) { case SIZE_MINIMIZED: { z = LZoomMin; break; } case SIZE_MAXIMIZED: { z = LZoomMax; break; } case SIZE_RESTORED: { z = LZoomNormal; break; } } if (z != d->Show) { OnZoom(d->Show = z); } } Status = LView::OnEvent(Msg) != 0; break; } case WM_CREATE: { if (d->AlwaysOnTop) SetAlwaysOnTop(true); PourAll(); OnCreate(); if (!_Default) { _Default = FindControl(IDOK); if (_Default) _Default->Invalidate(); } d->InCreate = false; if (d->Wp) { PostEvent(M_SET_WINDOW_PLACEMENT); } break; } case WM_WINDOWPOSCHANGED: { d->Wp.Reset(); Status = LView::OnEvent(Msg) != 0; break; } case WM_QUERYENDSESSION: case WM_CLOSE: { bool QuitApp; bool OsShuttingDown = Msg->Msg() == WM_QUERYENDSESSION; if (QuitApp = OnRequestClose(OsShuttingDown)) { Quit(); } if (Msg->Msg() == WM_CLOSE) { return 0; } else { return QuitApp; } break; } case WM_SYSCOMMAND: { if (Msg->a == SC_CLOSE) { if (OnRequestClose(false)) { Quit(); } return 0; } else { Status = LView::OnEvent(Msg) != 0; } break; } case WM_DROPFILES: { HDROP hDrop = (HDROP) Msg->a; if (hDrop) { LArray FileNames; int Count = 0; Count = DragQueryFileW(hDrop, -1, NULL, 0); for (int i=0; i 0) { FileNames.Add(WideToUtf8(FileName)); } } OnReceiveFiles(FileNames); FileNames.DeleteArrays(); } break; } case M_HANDLEMOUSEMOVE: { // This receives events fired from the LMouseHookPrivate class so that // non-LGI windows create mouse hook events as well. LTempView v((OsView)Msg->B()); LMouse m; m.x = LOWORD(Msg->A()); m.y = HIWORD(Msg->A()); HandleViewMouse(&v, m); break; } case M_COMMAND: { HWND OurWnd = Handle(); // copy onto the stack, because // we might lose the 'this' object in the // OnCommand handler which would delete // the memory containing the handle. Status = OnCommand((int) Msg->a, 0, (OsView) Msg->b); if (!IsWindow(OurWnd)) { // The window was deleted so break out now break; } // otherwise fall thru to the LView handler } default: { Status = (int) LView::OnEvent(Msg); break; } } return Status; } LPoint LWindow::GetDpi() { if (!d->Dpi.x) d->Dpi = LGetDpiForWindow(_View); return d->Dpi; } LPointF LWindow::GetDpiScale() { auto Dpi = GetDpi(); LPointF r( Dpi.x / 96.0, Dpi.y / 96.0 ); return r; } LRect &LWindow::GetPos() { if (_View && IsZoomed(_View)) { static LRect r; RECT rc; GetWindowRect(_View, &rc); r = rc; return r; } return Pos; } void LWindow::OnPosChange() { PourAll(); } bool LWindow::RegisterHook(LView *Target, LWindowHookType EventType, int Priority) { bool Status = false; if (Target && EventType) { auto i = d->GetHookIndex(Target, true); if (i >= 0) { d->Hooks[i].Flags = EventType; Status = true; } } return Status; } LViewI *LWindow::GetDefault() { return _Default; } void LWindow::SetDefault(LViewI *v) { #if WINNATIVE LButton *Btn; if (Btn = dynamic_cast(_Default)) Btn->Default(false); #endif _Default = v; #if WINNATIVE if (Btn = dynamic_cast(_Default)) Btn->Default(true); #endif } bool LWindow::UnregisterHook(LView *Target) { auto i = d->GetHookIndex(Target); if (i >= 0) { d->Hooks.DeleteAt(i); return true; } return false; } bool LWindow::SerializeState(LDom *Store, const char *FieldName, bool Load) { if (!Store || !FieldName) return false; #if DEBUG_SERIALIZE_STATE LgiTrace("LWindow::SerializeState(%p, %s, %i)\n", Store, FieldName, Load); #endif if (Load) { LVariant v; if (Store->GetValue(FieldName, v) && v.Str()) { LRect Position(0, 0, -1, -1); LWindowZoom State = LZoomNormal; #if DEBUG_SERIALIZE_STATE LgiTrace("\t::SerializeState:%i v=%s\n", __LINE__, v.Str()); #endif for (auto Var: v.LStr().SplitDelimit(";")) { auto v = Var.SplitDelimit("=", 1); if (v.Length() == 2) { if (v[0].Equals("State")) State = (LWindowZoom)v[1].Int(); else if (v[0].Equals("Pos")) { LRect r; r.SetStr(v[1]); if (r.Valid()) Position = r; } } else return false; } #if DEBUG_SERIALIZE_STATE LgiTrace("\t::SerializeState:%i State=%i, Pos=%s\n", __LINE__, State, Position.GetStr()); #endif // Apply any shortcut override int Show = LAppInst->GetShow(); if (Show == SW_SHOWMINIMIZED || Show == SW_SHOWMINNOACTIVE || Show == SW_MINIMIZE) { State = LZoomMin; } else if (Show == SW_SHOWMAXIMIZED || Show == SW_MAXIMIZE) { State = LZoomMax; } LAutoPtr Wp(new WINDOWPLACEMENT); if (Wp) { ZeroObj(*Wp.Get()); Wp->length = sizeof(WINDOWPLACEMENT); if (Visible()) { if (State == LZoomMax) { Wp->showCmd = SW_SHOWMAXIMIZED; } else if (State == LZoomMin) { Wp->showCmd = SW_MINIMIZE; } else { Wp->showCmd = d->ShowCmd; } } else { Wp->showCmd = SW_HIDE; d->Show = State; } LRect DefaultPos(100, 100, 900, 700); if (Position.Valid()) { LArray Displays; LRect AllDisplays; bool PosOk = true; if (LGetDisplays(Displays, &AllDisplays)) { // Check that the position is on one of the screens PosOk = false; for (unsigned i=0; ir; Int.Intersection(&Position); if (Int.Valid() && Int.X() > 20 && Int.Y() > 20) { PosOk = true; break; } } Displays.DeleteObjects(); } if (PosOk) Pos = Position; else Pos = DefaultPos; } else Pos = DefaultPos; Wp->rcNormalPosition = Pos; #if DEBUG_SERIALIZE_STATE LgiTrace("%s:%i - SetWindowPlacement, pos=%s, show=%i\n", _FL, Pos.GetStr(), Wp->showCmd); #endif SetWindowPlacement(Handle(), Wp); if (d->InCreate) d->Wp = Wp; } } else return false; } else { char s[256]; LWindowZoom State = GetZoom(); LRect Position; if (Handle()) { WINDOWPLACEMENT Wp; ZeroObj(Wp); Wp.length = sizeof(Wp); GetWindowPlacement(Handle(), &Wp); Position = Wp.rcNormalPosition; } else { // A reasonable fall back if we don't have a window... Position = GetPos(); } sprintf_s(s, sizeof(s), "State=%i;Pos=%s", State, Position.GetStr()); #if DEBUG_SERIALIZE_STATE LgiTrace("\t::SerializeState:%i s='%s'\n", __LINE__, s); #endif LVariant v = s; if (!Store->SetValue(FieldName, v)) return false; } return true; } void LWindow::OnTrayClick(LMouse &m) { if (m.Down() || m.IsContextMenu()) { LSubMenu RClick; OnTrayMenu(RClick); if (GetMouse(m, true)) { #if WINNATIVE SetForegroundWindow(Handle()); #endif int Result = RClick.Float(this, m); #if WINNATIVE PostMessage(Handle(), WM_NULL, 0, 0); #endif OnTrayMenuResult(Result); } } }