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,474 +1,480 @@ /** \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) && (!HAIKU64) // For all Mac and Haiku32 #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 (0) GV_NULL, /// 32-bit integer (1) GV_INT32, /// 64-bit integer (2) GV_INT64, /// true or false boolean. (3) GV_BOOL, /// C++ double (4) GV_DOUBLE, /// Null terminated string value (5) GV_STRING, /// Block of binary data (6) GV_BINARY, /// List of LVariant (7) GV_LIST, /// Pointer to LDom object (8) GV_DOM, /// DOM reference, ie. a variable in a DOM object (9) GV_DOMREF, /// Untyped pointer (10) GV_VOID_PTR, /// LDateTime class. (11) GV_DATETIME, /// Hash table class, containing pointers to LVariants (12) GV_HASHTABLE, // Scripting language operator (13) GV_OPERATOR, // Custom scripting lang type (14) GV_CUSTOM, // Wide string (15) GV_WSTRING, // LSurface ptr (16) GV_LSURFACE, /// Pointer to LView (17) GV_LVIEW, /// Pointer to LMouse (18) GV_LMOUSE, /// Pointer to LKey (19) GV_LKEY, /// Pointer to LStream (20) GV_STREAM, /// The maximum value for the variant type. (21) /// (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, + OpBitwiseAnd, + OpBitwiseOr, + OpBitwiseNegate, + OpBitwiseXor, + OpShiftLeft, + OpShiftRight, }; 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; const char *GetClass() override { return "LCustomType.CustomField"; } ssize_t Sizeof(); bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; }; public: struct Method : public LDom { LString Name; LArray Params; size_t Address = -1; int FrameSize = -1; const char *GetClass() override { return "LCustomType.Method"; } }; 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(); const char *GetClass() override { return "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) override; bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LScriptArguments &Args) override; }; /// 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_LVIEW, #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_LVIEW 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 /// behavior 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_LVIEW ? 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; ssize_t Address; public: static LStream NullConsole; LScriptArguments(LVirtualMachineI *vm, LVariant *ret = NULL, LStream *console = NULL, ssize_t address = -1); ~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); LDom *DomAt(size_t i); }; #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,2081 +1,2187 @@ /* 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 LResolveRef(nm) LVariant *nm = #else #define Resolve() c.r++ #define LResolveRef(nm) // LVarRef * #endif default: { - #if VM_DECOMP + #if 1// VM_DECOMP if (Log) - Log->Print("\t%p Unknown instruction %i (0x%x)\n", + Log->Print("\t%p Unknown instruction %i (%s)\n", CurrentScriptAddress - 1, - c.u8[-1], c.u8[-1]); + c.u8[-1], InstToString((LInstruction)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 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 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 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 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 IBitwiseOr: +{ + #if VM_DECOMP + if (Log) + Log->Print("%p BitwiseOr %s | %s\n", + CurrentScriptAddress - 1, + c.r[0].GetStr(), + c.r[1].GetStr()); + #endif + + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); + + #ifdef VM_EXECUTE + *Dst = Dst->CastInt64() | Src->CastInt64(); + #endif + break; +} +case IBitwiseAnd: +{ + #if VM_DECOMP + if (Log) + Log->Print("%p BitwiseAnd %s & %s\n", + CurrentScriptAddress - 1, + c.r[0].GetStr(), + c.r[1].GetStr()); + #endif + + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); + + #ifdef VM_EXECUTE + *Dst = Dst->CastInt64() & Src->CastInt64(); + #endif + break; +} +case IBitwiseXor: +{ + #if VM_DECOMP + if (Log) + Log->Print("%p BitwiseXor %s & %s\n", + CurrentScriptAddress - 1, + c.r[0].GetStr(), + c.r[1].GetStr()); + #endif + + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); + + #ifdef VM_EXECUTE + *Dst = Dst->CastInt64() ^ Src->CastInt64(); + #endif + break; +} +case IBitwiseNegate: +{ + #if VM_DECOMP + if (Log) + Log->Print("%p BitwiseNegate %s = ~%s\n", + CurrentScriptAddress - 1, + c.r[0].GetStr(), + c.r[0].GetStr()); + #endif + + LResolveRef(Dst) Resolve(); + #ifdef VM_EXECUTE + *Dst = ~Dst->CastInt64(); + #endif + break; +} +case IShiftLeft: +{ + #if VM_DECOMP + if (Log) + Log->Print("%p ShiftLeft %s << %s\n", + CurrentScriptAddress - 1, + c.r[0].GetStr(), + c.r[1].GetStr()); + #endif + + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); + + #ifdef VM_EXECUTE + *Dst = Dst->CastInt64() << Src->CastInt64(); + #endif + break; +} +case IShiftRight: +{ + #if VM_DECOMP + if (Log) + Log->Print("%p Plus %s += %s\n", + CurrentScriptAddress - 1, + c.r[0].GetStr(), + c.r[1].GetStr()); + #endif + + LResolveRef(Dst) Resolve(); + LResolveRef(Src) Resolve(); + + #ifdef VM_EXECUTE + *Dst = Dst->CastInt64() >> Src->CastInt64(); + #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 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 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 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 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 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 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 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 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 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 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 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 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 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 LResolveRef(Ret) Resolve(); uint16 Args = *c.u16++; #ifdef VM_EXECUTE LScriptArguments Arg(Vm, Ret, NULL, CurrentScriptAddress); #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)) { 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 (Log) Log->Print( "%s ICallMethod error: Method '%s' failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), Meth->Method.Get()); SetScriptError; } } ON_EXCEPTION(Arg); #endif break; } case ICallScript: { int32 FuncAddr = *c.i32++; if (FuncAddr < 0 || (uint32_t)FuncAddr >= Code->ByteCode.Length()) { 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 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 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 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 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 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 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 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 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 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 LResolveRef(Dst) Resolve(); LResolveRef(Dom) Resolve(); LResolveRef(Name) Resolve(); #ifdef VM_EXECUTE LResolveRef(Args) Resolve(); int ArgCount = Args->CastInt32(); char *sName = Name->Str(); CheckParam(sName) if (Dom->Type == GV_CUSTOM) { #define DEBUG_CUSTOM_METHOD_CALL 1 auto t = Dom->Value.Custom.Dom; CheckParam(t); auto m = t->GetMethod(sName); CheckParam(m); CheckParam(m->Params.Length() == ArgCount); // 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); #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 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()); #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, NULL, CurrentScriptAddress); 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); auto Ret = dom->CallMethod(sName, Arg); if (!Ret) { Dst->Empty(); if (Log) Log->Print("%s IDomCall warning: %s(...) failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; } break; } case GV_DATETIME: { CheckParam(Dom->Value.Date); auto 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]) { auto Index = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; auto v = new LVariant; *v = *Arg[0]; Dom->Value.Lst->Insert(v, Index); } break; } case ContainerDelete: { for (unsigned i=0; iCastInt32(); auto Elem = Dom->Value.Lst->ItemAt(n); if (Elem) { Dom->Value.Lst->Delete(Elem); DeleteObj(Elem); } } } break; } case ContainerHasKey: { if (Arg.Length() > 0 && Arg[0]) { auto Index = Arg[0]->CastInt32(); *Dst = (bool) (Index >= 0 && Index < (int)Dom->Value.Lst->Length()); } else { *Dst = false; } break; } case ContainerHasItem: { bool found = false; if (Arg.Length() > 0 && Arg[0]) { auto item = Arg[0]; for (auto v: *Dom->Value.Lst) { if (*v == *item) { found = true; break; } } } *Dst = found; break; } case ContainerSort: { auto 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; } 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; } case ContainerHasItem: { bool found = false; if (Arg.Length() > 0 && Arg[0]) { auto item = Arg[0]; for (auto p: *Dom->Value.Hash) { if (*p.value == *item) { found = true; break; } } } *Dst = found; break; } default: { Dst->Empty(); if (Log) Log->Print("%s IDomCall warning: Unexpected hashtable member '%s'.\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 ObjLength: { char *s = Dom->Str(); *Dst = (int) (s ? strlen(s) : 0); break; } case StrJoin: { 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; } 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; 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; } 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; 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; 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; 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 (pos) *Dst = (int64) (pos - s); else *Dst = -1; break; } case StrRfind: { 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) { *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) { cur = (end) ? Strnstr(cur, sub, end - cur) : Strstr(cur, sub); if (cur) { pos = cur; cur += sublen; } else break; } 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 = LWhiteSpace; 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: { 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; } default: { Dst->Empty(); if (Log) Log->Print("%p IDomCall warning: Unexpected string member %s (%s:%i).\n", CurrentScriptAddress, sName, _FL); 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; } } ON_EXCEPTION(Arg); #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; } int Args = Count->CastInt32(); for (int i=0; iPrint("%s%s", i ? ", " : "", c.r->GetStr()); #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 LResolveRef \ No newline at end of file diff --git a/src/common/Coding/LexCpp.cpp b/src/common/Coding/LexCpp.cpp --- a/src/common/Coding/LexCpp.cpp +++ b/src/common/Coding/LexCpp.cpp @@ -1,246 +1,250 @@ #include "lgi/common/LexCpp.h" #include #include #define iswhite(s) (s && strchr(White, s) != 0) #define isword(s) (s && (IsDigit(s) || IsAlpha(s) || (s) == '_') ) #define skipws(s) while (*s) \ { \ if (*s == '\n') \ { \ if (LineCount) *LineCount = *LineCount + 1; \ s++; \ } \ else if (*s == ' ' || *s == '\t' || *s == '\r') \ s++; \ else \ break; \ } char16 *LexStrdup(void *context, const char16 *start, ssize_t len) { if (len < 0) len = StrlenW(start); char16 *n = new char16[len + 1]; if (n) { memcpy(n, start, sizeof(*n) * len); n[len] = 0; } return n; } char16 *LexNoReturn(void *context, const char16 *start, ssize_t len) { return NULL; } // Returns the next C++ token in 's' // // If 'ReturnString' is false then the next token is skipped and // not returned. i.e. the return is always NULL but 's' is moved. char16 *LexCpp ( char16 *&s, LexCppStrdup Strdup, void *Context, int *LineCount ) { LexAgain: skipws(s); if (*s == '#') { // Pre-processor command char16 *Start = s++; // Skip any whitespace while (*s == ' ' || *s == '\t') { Start = s; *s++ = '#'; } // Non whitespace while (*s && IsAlpha(*s)) { s++; } return Strdup(Context, Start, s-Start); } else if (*s == '_' || IsAlpha(*s)) { // Identifier char16 *Start = s++; while ( *s && ( *s == '_' || *s == ':' || IsAlpha(*s) || IsDigit(*s) ) ) { s++; } return Strdup(Context, Start, s-Start); } else if (s[0] == '/' && s[1] == '/') { // C++ Comment s += 2; while (*s) { if (*s == '\n') { if (LineCount) *LineCount = *LineCount+1; s++; goto LexAgain; } s++; } return NULL; } else if (s[0] == '/' && s[1] == '*') { // C comment s += 2; while (*s) { if (*s == '\n') { if (LineCount) *LineCount = *LineCount+1; } else if (s[0] == '*' && s[1] == '/') { s += 2; goto LexAgain; } s++; } return NULL; } else if ( (s[0] == '-' && s[1] == '>') || (s[0] == '|' && s[1] == '|') || (s[0] == '&' && s[1] == '&') || (s[0] == '+' && s[1] == '+') || (s[0] == '-' && s[1] == '-') || (s[0] == '/' && s[1] == '=') || (s[0] == '-' && s[1] == '=') || (s[0] == '*' && s[1] == '=') || (s[0] == '+' && s[1] == '=') || (s[0] == '^' && s[1] == '=') || (s[0] == '>' && s[1] == '=') || (s[0] == '<' && s[1] == '=') || (s[0] == '-' && s[1] == '>') || (s[0] == '=' && s[1] == '=') || (s[0] == '!' && s[1] == '=') + || + (s[0] == '<' && s[1] == '<') + || + (s[0] == '>' && s[1] == '>') ) { // 2 char delimiter char16 *Status = Strdup(Context, s, 2); s += 2; return Status; } else if (IsDigit(*s) || (*s == '-' && IsDigit(s[1]) ) ) { // Constant char16 *Start = s; bool IsHex = false; // Skip hex prefix... if (s[0] == '0' && tolower(s[1]) == 'x') { s += 2; IsHex = true; } if (*s == '-') s++; // Seek to end of number while ( *s && ( *s == '.' || *s == 'e' || IsDigit(*s) || ( IsHex && strchr("abcdef", tolower(*s)) ) ) ) { s++; } return Strdup(Context, Start, s-Start); } else if (*s && strchr("-()*[]&,{};:=!<>?.\\+/%^|~@", *s)) { // Delimiter return Strdup(Context, s++, 1); } else if (*s && strchr("\"\'", *s)) { // String char16 Delim = *s; char16 *Start = s++; while (*s) { if (*s == '\\') { s += 2; } else if (*s == Delim) { s++; break; } else { s++; } } return Strdup(Context, Start, s-Start); } return NULL; }; \ No newline at end of file diff --git a/src/common/Coding/ScriptCompiler.cpp b/src/common/Coding/ScriptCompiler.cpp --- a/src/common/Coding/ScriptCompiler.cpp +++ b/src/common/Coding/ScriptCompiler.cpp @@ -1,3873 +1,3856 @@ /// \file #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/LexCpp.h" #include "lgi/common/LgiString.h" #include "ScriptingPriv.h" // #define DEBUG_SCRIPT_FILE "Mail Filters Menu.script" #define GetTok(c) ((c) < Tokens.Length() ? Tokens[c] : NULL) #define GetTokType(c) ((c) < Tokens.Length() ? ExpTok.Find(Tokens[c]) : TNone) #define GV_VARIANT GV_MAX const char *sDebugger = "Debugger"; int LFunctionInfo::_Infos = 0; enum LTokenType { #define _(type, str) T##type, #include "TokenType.h" #undef _ }; struct LinkFixup { int Tok; size_t Offset; int Args; LFunctionInfo *Func; }; struct Node { typedef LArray NodeExp; struct VariablePart { LVariant Name; NodeExp Array; bool Call; LArray Args; VariablePart() { Call = false; } ~VariablePart() { Args.DeleteObjects(); } }; // Hierarchy NodeExp Child; // One of the following are valid: LOperator Op = OpNull; // -or- bool Constant = false; int Tok = -1; LArray Lst; LTokenType ConstTok = TNone; // -or- LFunc *ContextFunc = NULL; LArray Args; // -or- LFunctionInfo *ScriptFunc = NULL; // -or- LArray Variable; // Used during building LVarRef Reg; LVarRef ArrayIdx; void Init() { Reg.Empty(); ArrayIdx.Empty(); } void SetOp(LOperator o, int t) { Init(); Op = o; Tok = t; } void SetConst(int t, LTokenType e) { Init(); Constant = true; Tok = t; ConstTok = e; } void SetConst(LArray &list_tokens, LTokenType e) { Init(); Constant = true; Lst = list_tokens; ConstTok = e; } void SetContextFunction(LFunc *m, int tok) { Init(); ContextFunc = m; Tok = tok; } void SetScriptFunction(LFunctionInfo *m, int tok) { Init(); ScriptFunc = m; Tok = tok; } void SetVar(char16 *Var, int t) { Init(); Variable[0].Name = Var; Tok = t; } bool IsVar() { return Variable.Length() > 0; } bool IsContextFunc() { return ContextFunc != 0; } bool IsScriptFunc() { return ScriptFunc != 0; } bool IsConst() { return Constant; } }; LCompiledCode::LCompiledCode() : Globals(SCOPE_GLOBAL), Debug(0, -1) { SysContext = NULL; UserContext = NULL; } LCompiledCode::LCompiledCode(LCompiledCode ©) : Globals(SCOPE_GLOBAL), Debug(0, -1) { *this = copy; } LCompiledCode::~LCompiledCode() { for (auto e: Externs) e->InUse = false; Externs.DeleteObjects(); } LCompiledCode &LCompiledCode::operator =(const LCompiledCode &c) { Globals = c.Globals; ByteCode = c.ByteCode; Types = c.Types; Debug = c.Debug; Methods = c.Methods; FileName = c.FileName; Source = c.Source; return *this; } LFunctionInfo *LCompiledCode::GetMethod(const char *Name, bool Create) { for (auto &m: Methods) { const char *Fn = m->GetName(); if (!strcmp(Fn, Name)) { return m; } } if (Create) { LAutoRefPtr n(new LFunctionInfo(Name)); if (n) { Methods.Add(n); return n; } } return 0; } int LCompiledCode::ObjectToSourceAddress(size_t ObjAddr) { auto Idx = Debug.Find(ObjAddr); return Idx < 0 ? 1 : Idx; } const char *LCompiledCode::AddrToSourceRef(size_t ObjAddr) { static char Status[256]; size_t Addr = ObjAddr; int LineNum = ObjectToSourceAddress(Addr); // Search for the start of the instruction... while (Addr > 0 && LineNum < 0) LineNum = ObjectToSourceAddress(--Addr); char *Dir = FileName ? strrchr(FileName, DIR_CHAR) : NULL; size_t PathLen = Dir ? Dir - FileName : 0; LString FileRef = FileName ? (PathLen > 24 ? Dir + 1 : FileName.Get()) : "(untitled)"; if (LineNum >= 0) sprintf_s( Status, sizeof(Status), "%s:%i", FileRef.Get(), LineNum); else sprintf_s(Status, sizeof(Status), "%s:0x%x", FileRef.Get(), (int)ObjAddr); return Status; } LVariant *LCompiledCode::Set(const char *Name, LVariant &v) { int i = Globals.Var(Name, true); if (i >= 0) { Globals[i] = v; return &Globals[i]; } return 0; } template void UnEscape(T *t) { T *i = t, *o = t; while (*i) { if (*i == '\\') { i++; switch (*i) { case '\\': *o++ = '\\'; i++; break; case 'n': *o++ = '\n'; i++; break; case 'r': *o++ = '\r'; i++; break; case 't': *o++ = '\t'; i++; break; case 'b': *o++ = '\b'; i++; break; } } else *o++ = *i++; } *o++ = 0; } class TokenRanges { LArray FileNames; struct Range { int Start, End; ssize_t File; int Line; }; LArray Ranges; char fl[MAX_PATH_LEN + 32]; public: TokenRanges() { Empty(); } void Empty() { Ranges.Length(0); } ssize_t Length() { return Ranges.Length(); } /// Gets the file/line at a given token const char *operator [](ssize_t Tok) { Range *r = NULL; if (Ranges.Length() == 0) return "#errNoRanges"; for (unsigned i=0; i= rng->Start && Tok <= rng->End) { r = rng; break; } } if (!r) r = &Ranges.Last(); if (r->File >= (ssize_t)FileNames.Length()) { LAssert(!"Invalid file index."); return "#err: invalid file index"; } else { sprintf_s(fl, sizeof(fl), "%s:%i", FileNames[r->File].Get(), r->Line); } return fl; } const char *GetLine(int Line) { if (Ranges.Length() > 0) { Range &r = Ranges.Last(); if (r.File < (ssize_t)FileNames.Length()) sprintf_s(fl, sizeof(fl), "%s:%i", FileNames[r.File].Get(), Line); else return "#err: invalid file index."; } else { sprintf_s(fl, sizeof(fl), "$unknown:%i", Line); } return fl; } ssize_t GetFileIndex(const char *FileName) { for (unsigned i=0; iFile != FileId || r->Line != Line) { // Start a new range... r = &Ranges.New(); r->Start = r->End = TokIndex; r->File = FileId; r->Line = Line; } else { r->End = TokIndex; } } }; /// Scripting language compiler implementation class LCompilerPriv : public LCompileTools, public LScriptUtils { LHashTbl, LVariantType> Types; size_t JumpLoc; LArray> FuncMem; public: - LScriptContext *SysCtx; - LScriptContext *UserCtx; - LCompiledCode *Code; - LStream *Log; + LScriptContext *SysCtx = NULL; + LScriptContext *UserCtx = NULL; + LCompiledCode *Code = NULL; + LStream *Log = NULL; LArray Tokens; TokenRanges Lines; - char16 *Script; + char16 *Script = NULL; LHashTbl, LFunc*> Methods; - uint64_t Regs; + uint64_t Regs = 0; LArray Scopes; LArray Fixups; LHashTbl, char16*> Defines; LHashTbl, LTokenType> ExpTok; - LDom *ScriptArgs; + LDom *ScriptArgs = NULL; LVarRef ScriptArgsRef; - bool ErrShowFirstOnly; + bool ErrShowFirstOnly = true; LArray ErrLog; - bool Debug; + bool Debug = false; #ifdef _DEBUG LString::Array RegAllocators; #endif LCompilerPriv() { - Debug = false; - ErrShowFirstOnly = true; - SysCtx = NULL; - UserCtx = NULL; - Code = 0; - Log = 0; - Script = 0; - Regs = 0; - ScriptArgs = NULL; ScriptArgsRef.Empty(); #define LNULL NULL #define _(type, str) if (str) ExpTok.Add(L##str, T##type); #include "TokenType.h" #undef _ #undef LNULL Types.Add("int", GV_INT32); Types.Add("short", GV_INT32); Types.Add("long", GV_INT32); Types.Add("int8", GV_INT32); Types.Add("int16", GV_INT32); Types.Add("int32", GV_INT32); Types.Add("int64", GV_INT64); Types.Add("uint8", GV_INT32); Types.Add("uint16", GV_INT32); Types.Add("uint32", GV_INT32); Types.Add("uint64", GV_INT64); Types.Add("bool", GV_BOOL); Types.Add("boolean", GV_BOOL); Types.Add("double", GV_DOUBLE); Types.Add("float", GV_DOUBLE); Types.Add("char", GV_STRING); Types.Add("LDom", GV_DOM); Types.Add("void", GV_VOID_PTR); Types.Add("LDateTime", GV_DATETIME); Types.Add("GHashTable", GV_HASHTABLE); Types.Add("LOperator", GV_OPERATOR); Types.Add("LView", GV_LVIEW); Types.Add("LMouse", GV_LMOUSE); Types.Add("LKey", GV_LKEY); Types.Add("LVariant", GV_VARIANT); // Types.Add("binary", GV_BINARY); // Types.Add("List", GV_LIST); // Types.Add("LDom&", GV_DOMREF); } ~LCompilerPriv() { Empty(); } void Empty() { SysCtx = NULL; UserCtx = NULL; DeleteObj(Code); - Log = 0; + Log = NULL; Tokens.DeleteArrays(); Lines.Empty(); DeleteArray(Script); Defines.DeleteArrays(); Methods.Empty(); } /// Prints the error message bool OnError ( /// The line number (less than 0) or Token index (greater than 0) int LineOrTok, /// The format for the string (printf) const char *Msg, /// Variable argument list ... ) { if (!ErrShowFirstOnly || ErrLog.Length() == 0) { char Buf[512]; va_list Arg; va_start(Arg, Msg); #ifndef WIN32 #define _vsnprintf vsnprintf #endif vsprintf_s(Buf, sizeof(Buf), Msg, Arg); Log->Print("%s - Error: %s\n", LineOrTok < 0 ? Lines.GetLine(-LineOrTok) : Lines[LineOrTok], Buf); va_end(Arg); ErrLog.New() = Buf; } return false; // Always return false to previous caller } void DebugInfo(int Tok) { const char *Line = Lines[Tok]; if (Line) { const char *c = strrchr(Line, ':'); if (c) { Code->Debug.Add(Code->ByteCode.Length(), ::atoi(c+1)); } } } /// Assemble a zero argument instruction bool Asm0(int Tok, uint8_t Op) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1)) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; } else return false; return true; } /// Assemble one arg instruction bool Asm1(int Tok, uint8_t Op, LVarRef a) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 5)) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; *p.r++ = a; } else return false; return true; } /// Assemble two arg instruction bool Asm2(int Tok, uint8_t Op, LVarRef a, LVarRef b) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 9)) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; *p.r++ = a; *p.r++ = b; } else return false; return true; } /// Assemble three arg instruction bool Asm3(int Tok, uint8_t Op, LVarRef a, LVarRef b, LVarRef c) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1 + (sizeof(LVarRef) * 3) )) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; *p.r++ = a; *p.r++ = b; *p.r++ = c; } else return false; return true; } /// Assemble four arg instruction bool Asm4(int Tok, uint8_t Op, LVarRef a, LVarRef b, LVarRef c, LVarRef d) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1 + (sizeof(LVarRef) * 4) )) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; if (!a.Valid()) AllocNull(a); *p.r++ = a; if (!b.Valid()) AllocNull(b); *p.r++ = b; if (!c.Valid()) AllocNull(c); *p.r++ = c; if (!d.Valid()) AllocNull(d); *p.r++ = d; } else return false; return true; } /// Assemble 'n' length arg instruction bool AsmN(int Tok, uint8_t Op, LArray &Args) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1 + (sizeof(LVarRef) * Args.Length()) )) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; for (unsigned i=0; iDefines.Find(wName); if (v) { // Is it a number? Cause we should really convert to an int if possible... #define INVALID_CONVERSION -11111 int64 i = INVALID_CONVERSION; if (!Strnicmp(v, L"0x", 2)) i = Atoi(v, 16, INVALID_CONVERSION); else if (IsDigit(*v) || strchr("-.", *v)) i = Atoi(v); if (i != INVALID_CONVERSION) Value = i; else Value = v; return true; } return false; } }; /// This will look at resolving the expression to something simpler before storing it... void SimplifyDefine(LScriptEngine &Eng, char16 *Name, LAutoWString &Value) { LString Exp = Value.Get(); if (Exp.Find("(") >= 0) // Filter out simple expressions... { LVariant Result; CompilerDefineSource Src(this); if (Eng.EvaluateExpression(&Result, &Src, Exp)) { auto Val = Result.CastString(); if (Val) { // LgiTrace("Simplify: %S -> %s\n", Value.Get(), Val); Value.Reset(Utf8ToWide(Val)); } } } } /// Convert the source from one big string into an array of tokens bool Lex(char *Source, const char *FileName) { char16 *w = Utf8ToWide(Source); if (!w) return OnError(0, "Couldn't convert source to wide chars."); LScriptEngine Eng(NULL, NULL, NULL); ssize_t FileIndex = Lines.GetFileIndex(FileName); int Line = 1; char16 *s = w, *t; while ((t = LexCpp(s, LexStrdup, NULL, &Line))) { if (*t == '#') { ssize_t Len; if (!StrnicmpW(t + 1, sInclude, Len = StrlenW(sInclude))) { LAutoWString Raw(LexCpp(s, LexStrdup)); LAutoWString File(TrimStrW(Raw, (char16*)L"\"\'")); if (File) { LVariant v; LString IncCode; v.OwnStr(File.Release()); if (UserCtx) { IncCode = UserCtx->GetIncludeFile(v.Str()); if (IncCode) { Lex(IncCode, v.Str()); } else { DeleteArray(t); return OnError(-Line, "Ctx failed to include '%s'", v.Str()); } } else { if (LIsRelativePath(v.Str())) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), FileName, ".."); LMakePath(p, sizeof(p), p, v.Str()); v = p; } if (LFileExists(v.Str())) { LFile f(v.Str()); if (f && (IncCode = f.Read())) { Lex(IncCode, v.Str()); } else { DeleteArray(t); return OnError(-Line, "Couldn't read '%s'", v.Str()); } } else { DeleteArray(t); return OnError(-Line, "Couldn't include '%s'", v.Str()); } } } else { OnError(-Line, "No file for #include."); } } else if (!StrnicmpW(t + 1, sDefine, Len = StrlenW(sDefine))) { LAutoWString Name(LexCpp(s, LexStrdup)); if (Name && IsAlpha(*Name)) { char16 *Start = s; while (*Start && strchr(LWhiteSpace, *Start)) Start++; char16 *Eol = StrchrW(Start, '\n'); if (!Eol) Eol = Start + StrlenW(Start); while (Eol > Start && strchr(LWhiteSpace, Eol[-1])) Eol--; LAutoWString Value(NewStrW(Start, Eol - Start)); SimplifyDefine(Eng, Name, Value); Defines.Add(Name, Value.Release()); s = Eol; } } DeleteArray(t); continue; } if (!ResolveDefine(t, FileIndex, Line)) { Lines.Add((int)Tokens.Length(), FileIndex, Line); Tokens.Add(t); } } // end of "while (t = LexCpp)" loop if (!Script) { Script = w; } else { DeleteArray(w); } return true; } /// Create a null var ref void AllocNull(LVarRef &r) { r.Scope = SCOPE_GLOBAL; if (Code->Globals.NullIndex < 0) Code->Globals.NullIndex = (int)Code->Globals.Length(); r.Index = Code->Globals.NullIndex; Code->Globals[r.Index].Type = GV_NULL; } /// Allocate a variant and ref LVariant *PreAllocVariant(LVarRef &r) { r.Scope = SCOPE_GLOBAL; r.Index = (int)Code->Globals.Length(); return &Code->Globals[r.Index]; } /// Allocate a constant double void AllocConst(LVarRef &r, double d) { r.Scope = SCOPE_GLOBAL; if (Code->Globals.Length()) { // Check for existing int LVariant *p = &Code->Globals[0]; LVariant *e = p + Code->Globals.Length(); while (p < e) { if (p->Type == GV_DOUBLE && p->Value.Dbl == d) { r.Index = (int) (p - &Code->Globals[0]); return; } p++; } } r.Index = (int)Code->Globals.Length(); Code->Globals[r.Index] = d; } /// Allocate a constant bool void AllocConst(LVarRef &r, bool b) { r.Scope = SCOPE_GLOBAL; if (Code->Globals.Length()) { // Check for existing int LVariant *p = &Code->Globals[0]; LVariant *e = p + Code->Globals.Length(); while (p < e) { if (p->Type == GV_BOOL && p->Value.Bool == b) { r.Index = (int)(p - &Code->Globals[0]); return; } p++; } } r.Index = (int)Code->Globals.Length(); Code->Globals[r.Index] = b; } /// Allocate a constant int void AllocConst(LVarRef &r, int64 i) { r.Scope = SCOPE_GLOBAL; LVariantType Type = i <= INT_MAX && i >= INT_MIN ? GV_INT32 : GV_INT64; if (Code->Globals.Length()) { // Check for existing int LVariant *p = &Code->Globals[0]; LVariant *e = p + Code->Globals.Length(); while (p < e) { if (p->Type == Type) { if (Type == GV_INT32 && p->Value.Int == i) { r.Index = (int) (p - &Code->Globals[0]); return; } else if (Type == GV_INT64 && p->Value.Int64 == i) { r.Index = (int) (p - &Code->Globals[0]); return; } } p++; } } // Allocate new global r.Index = (int)Code->Globals.Length(); if (Type == GV_INT32) Code->Globals[r.Index] = (int32)i; else Code->Globals[r.Index] = i; } void AllocConst(LVarRef &r, int i) { AllocConst(r, (int64)i); } /// Allocate a constant string void AllocConst(LVarRef &r, char *s, ssize_t len = -1) { LAssert(s != 0); if (len < 0) len = strlen(s); r.Scope = SCOPE_GLOBAL; r.Index = (int)Code->Globals.Length(); for (unsigned i=0; iGlobals.Length(); i++) { if (Code->Globals[i].Type == GV_STRING) { char *c = Code->Globals[i].Str(); if (*s == *c && strcmp(s, c) == 0) { r.Index = i; return; } } } LVariant &v = Code->Globals[r.Index]; v.Type = GV_STRING; if ((v.Value.String = NewStr(s, len))) { UnEscape(v.Value.String); } } /// Allocate a constant wide string void AllocConst(LVarRef &r, char16 *s, ssize_t len) { LAssert(s != 0); char *utf = WideToUtf8(s, len); if (!utf) utf = NewStr(""); r.Scope = SCOPE_GLOBAL; r.Index = (int)Code->Globals.Length(); for (unsigned i=0; iGlobals.Length(); i++) { if (Code->Globals[i].Type == GV_STRING) { char *c = Code->Globals[i].Str(); if (*utf == *c && strcmp(utf, c) == 0) { r.Index = i; DeleteArray(utf); return; } } } LVariant &v = Code->Globals[r.Index]; v.Type = GV_STRING; if ((v.Value.String = utf)) { UnEscape(v.Value.String); } } /// Find a variable by name, creating it if needed LVarRef FindVariable(LVariant &Name, bool Create) { LVarRef r = {0, -1}; // Look for existing variable... ssize_t i; for (i=Scopes.Length()-1; i>=0; i--) { r.Index = Scopes[i]->Var(Name.Str(), false); if (r.Index >= 0) { r.Scope = Scopes[i]->Scope; return r; } } // Create new variable on most recent scope i = Scopes.Length() - 1; r.Index = Scopes[i]->Var(Name.Str(), Create); if (r.Index >= 0) { r.Scope = Scopes[i]->Scope; } return r; } /// Build asm to assign a var ref bool AssignVarRef(Node &n, LVarRef &Value) { /* Examples and what their assembly should look like: a = Value Assign a <- Value a[b] = Value R0 = AsmExpression(b); ArraySet a[R0] <- Value a[b].c = Value R0 = AsmExpression(b); R0 = ArrayGet a[R0] DomSet(R0, "c", Null, Value) a[b].c[d] R0 = AsmExpression(b); R0 = ArrayGet a[R0]; R1 = AsmExpression(d); R0 = DomGet(R0, "c", R1); ArraySet R0[R1] = Value a.b[c].d = Value R1 = AsmExpression(c); R0 = DomGet(a, "b", R1); DomSet(R1, "d", Null, Value) // resolve initial array if (parts > 1 && part[0].array) { // cur = ArrayGet(part[0].var, part[0].array) } else { cur = part[0] } // dom loop over loop (p = 0 to parts - 2) { if (part[p+1].array) arr = exp(part[p+1].array) else arr = null cur = DomGet(cur, part[p+1].var, arr) } // final part if (part[parts-1].arr) { arr = exp(part[parts-1].arr) ArraySet(cur, arr) } else { DomSet(cur, part[parts-1].var, null, value) } */ if (!Value.Valid()) { LAssert(!"Invalid value to assign.\n"); return false; } if (!n.IsVar()) { LAssert(!"Target must be a variable."); return false; } // Gets the first part of the variable. LVarRef Cur = FindVariable(n.Variable[0].Name, true); if (!Cur.Valid()) return false; if (n.Variable.Length() > 1) { // Do any initial array dereference if (n.Variable[0].Array.Length()) { // Assemble the array index's expression into 'Idx' LVarRef Idx; Idx.Empty(); if (!AsmExpression(&Idx, n.Variable[0].Array)) return OnError(n.Tok, "Error creating bytecode for array index."); LVarRef Dst = Cur; if (!Dst.IsReg()) { AllocReg(Dst, n.Tok, _FL); } // Assemble array load instruction Asm3(n.Tok, IArrayGet, Dst, Cur, Idx); Cur = Dst; // Cleanup DeallocReg(Idx); } // Do all the DOM "get" instructions unsigned p; for (p=0; pOwnStr(NewStrW(t + 1, Len - 2)); } else if (StrchrW(t, '.')) { // double *v = atof(t); } else if (t[0] == '0' && tolower(t[1]) == 'x') { // hex integer *v = htoi(t + 2); } else { // decimal integer int64 i = Atoi(t); if (i <= INT_MAX && i >= INT_MIN) *v = (int32)i; else *v = i; } return true; } /// Convert a token stream to a var ref bool TokenToVarRef(Node &n, LVarRef *&LValue) { if (n.Reg.Valid()) { if (LValue && n.Reg != *LValue) { // Need to assign to LValue LAssert(*LValue != n.Reg); Asm2(n.Tok, IAssign, *LValue, n.Reg); } } else { if (n.IsVar()) { // Variable unsigned p = 0; bool ArrayDeindexed = false; bool HasScriptArgs = Scopes.Length() <= 1 && ScriptArgs != NULL; LVarRef v = FindVariable(n.Variable[p].Name, /*!HasScriptArgs ||*/ LValue != NULL); if (v.Index < 0) { if (HasScriptArgs) { if (AllocReg(v, n.Tok, _FL)) { char16 *VarName = GetTok((unsigned)n.Tok); if (!ScriptArgsRef.Valid()) { // Setup the global variable to address the script argument variable ScriptArgsRef.Scope = SCOPE_GLOBAL; ScriptArgsRef.Index = (int)Code->Globals.Length(); LVariant &v = Code->Globals[ScriptArgsRef.Index]; v.Type = GV_DOM; v.Value.Dom = ScriptArgs; } LVarRef Name, Array; AllocConst(Name, VarName, -1); if (n.Variable[p].Array.Length()) { if (!AsmExpression(&Array, n.Variable[p].Array)) return OnError(n.Tok, "Can't assemble array expression."); ArrayDeindexed = true; } else AllocNull(Array); Asm4(n.Tok, IDomGet, v, ScriptArgsRef, Name, Array); } else return false; } else { Node::VariablePart &vp = n.Variable[p]; return OnError(n.Tok, "Undefined variable: %s", vp.Name.Str()); } } else { if (v.Scope == SCOPE_OBJECT) { // We have to load the variable into a register LVarRef Reg, ThisPtr, MemberIndex, Null; if (!AllocReg(Reg, n.Tok, _FL)) return OnError(n.Tok, "Couldn't alloc register."); ThisPtr.Scope = SCOPE_LOCAL; ThisPtr.Index = 0; AllocConst(MemberIndex, v.Index); AllocNull(Null); Asm4(n.Tok, IDomGet, Reg, ThisPtr, MemberIndex, Null); v = Reg; // Object variable now in 'Reg' } if (n.ConstTok == TTypeId) { if (!v.IsReg()) { // Because we are casting to it's DOM ptr, // make sure it's a register first so we don't lose the // actual variable. LVarRef reg; if (!AllocReg(reg, n.Tok, _FL)) return OnError(n.Tok, "Couldn't alloc register."); LAssert(reg != v); Asm2(n.Tok, IAssign, reg, v); v = reg; } // Casting to DOM will give as access to the type info for a LCustomType. // This currently doesn't work with other types :( Asm1(n.Tok, ICast, v); Code->ByteCode.Add(GV_DOM); } } n.Reg = v; LValue = NULL; LAssert(v.Scope != SCOPE_OBJECT); // Does it have an array deref? if (!ArrayDeindexed && n.Variable[p].Array.Length()) { // Evaluate the array indexing expression if (!AsmExpression(&n.ArrayIdx, n.Variable[p].Array)) { return OnError(n.Tok, "Error creating byte code for array index."); } // Do we need to create code to load the value from the array? LVarRef Val; if (AllocReg(Val, n.Tok, _FL)) { Asm3(n.Tok, IArrayGet, Val, n.Reg, n.ArrayIdx); n.Reg = Val; } else return OnError(n.Tok, "Error allocating register."); } // Load DOM parts... for (++p; p Args; Node::VariablePart &Part = n.Variable[p]; Name.Empty(); Arr.Empty(); char *nm = Part.Name.Str(); AllocConst(Name, nm, strlen(nm)); if (Part.Array.Length()) { if (!AsmExpression(&Arr, Part.Array)) { return OnError(n.Tok, "Can't assemble array expression."); } } else if (Part.Call) { for (unsigned i=0; i Call; Call[0] = Dst; Call[1] = n.Reg; Call[2] = Name; // This must always be a global, the decompiler requires access // to the constant to know how many arguments to print. And it // can only see the globals. AllocConst(Call[3], (int)Args.Length()); Call.Add(Args); AsmN(n.Tok, IDomCall, Call); } else { Asm4(n.Tok, IDomGet, Dst, n.Reg, Name, Arr); } n.Reg = Dst; } } else if (n.IsConst()) { // Constant switch (n.ConstTok) { case TTrue: { AllocConst(n.Reg, true); break; } case TFalse: { AllocConst(n.Reg, false); break; } case TNull: { AllocNull(n.Reg); break; } default: { if (n.Lst.Length()) { // List/array constant LVariant *v = PreAllocVariant(n.Reg); if (v) { v->SetList(); for (unsigned i=0; i a(new LVariant); if (!ConvertStringToVariant(a, t)) break; v->Value.Lst->Insert(a.Release()); } } } else { char16 *t = Tokens[n.Tok]; if (*t == '\"' || *t == '\'') { // string ssize_t Len = StrlenW(t); AllocConst(n.Reg, t + 1, Len - 2); } else if (StrchrW(t, '.')) { // double AllocConst(n.Reg, atof(t)); } else if (t[0] == '0' && tolower(t[1]) == 'x') { // hex integer AllocConst(n.Reg, htoi(t + 2)); } else { // decimal integer AllocConst(n.Reg, Atoi(t)); } } break; } } LValue = NULL; } else if (n.IsContextFunc()) { // Method call, create byte codes to put func value into n.Reg LVarRef *OutRef; if (LValue) { OutRef = LValue; } else { if (!AllocReg(n.Reg, n.Tok, _FL)) { return OnError(n.Tok, "Can't allocate register for method return value."); } OutRef = &n.Reg; } DebugInfo(n.Tok); LArray a; for (unsigned i=0; iByteCode.Length(); ssize_t Size = 1 + sizeof(LFunc*) + sizeof(LVarRef) + 2 + (a.Length() * sizeof(LVarRef)); Code->ByteCode.Length(Len + Size); LScriptPtr p; uint8_t *Start = &Code->ByteCode[Len]; p.u8 = Start; *p.u8++ = ICallMethod; *p.fn++ = n.ContextFunc; n.ContextFunc->InUse = true; *p.r++ = *OutRef; *p.u16++ = (uint16) n.Args.Length(); for (unsigned i=0; iGetName(); if (*FnName == 'D' && !_stricmp(FnName, sDebugger)) { Asm0(n.Tok, IDebug); return true; } if (n.ScriptFunc->GetParams().Length() != n.Args.Length()) { if (n.ScriptFunc->ValidStartAddr()) { // Function is already defined and doesn't have the right arg count... return OnError( n.Tok, "Wrong number of parameters: %i (%s expects %i)", n.Args.Length(), FnName, n.ScriptFunc->GetParams().Length()); } else { // Maybe it's defined later or in a separate file? // Or maybe it's just not defined at all? // Can't know until later... flag it as a unresolved external... // // Allow the assembly for the call to occur. } } // Call to a script function, create byte code to call function LVarRef *OutRef; if (LValue) { OutRef = LValue; } else { if (!AllocReg(n.Reg, n.Tok, _FL)) { return OnError(n.Tok, "Can't allocate register for method return value."); } OutRef = &n.Reg; } DebugInfo(n.Tok); LArray a; for (unsigned i=0; iByteCode.Length(); size_t Size = 1 + // instruction sizeof(uint32_t) + // address of function sizeof(uint16) + // size of frame sizeof(LVarRef) + // return value 2 + // number of args (a.Length() * sizeof(LVarRef)); // args Code->ByteCode.Length(Len + Size); LScriptPtr p; uint8_t *Start = &Code->ByteCode[Len]; p.u8 = Start; *p.u8++ = ICallScript; if (n.ScriptFunc->ValidStartAddr() && n.ScriptFunc->ValidFrameSize()) { // Compile func address straight into code... *p.u32++ = n.ScriptFunc->GetStartAddr(); *p.u16++ = n.ScriptFunc->GetFrameSize(); } else { // Add link time fix up LinkFixup &Fix = Fixups.New(); Fix.Tok = n.Tok; Fix.Args = (int)n.Args.Length(); Fix.Offset = p.u8 - &Code->ByteCode[0]; Fix.Func = n.ScriptFunc; *p.u32++ = 0; *p.u16++ = 0; } *p.r++ = *OutRef; *p.u16++ = (uint16) n.Args.Length(); for (unsigned i=0; i e(new Node::NodeExp); if (!e) return OnError(Cur, "Mem alloc error."); if (!Expression(Cur, *e)) return OnError(Cur, "Couldn't parse func call argument expression."); vp.Args.Add(e.Release()); t = GetTok(Cur); if (!t) return OnError(Cur, "Unexpected end of file."); if (!StricmpW(t, sComma)) Cur++; else if (!StricmpW(t, sEndRdBracket)) break; else return OnError(Cur, "Expecting ',', didn't get it."); } } t = GetTok(Cur+1); } // Check for DOM operator... if (StricmpW(t, sPeriod) == 0) { // Got Dom operator Cur += 2; t = GetTok(Cur); if (!t) return OnError(Cur, "Unexpected eof."); Var.Variable.New().Name = t; } else break; } return true; } /// Parse expression into a node tree bool Expression(uint32_t &Cur, LArray &n, int Depth = 0) { if (Cur >= Tokens.Length()) return OnError(Cur, "Unexpected end of file."); char16 *t; bool PrevIsOp = true; while ((t = Tokens[Cur])) { LTokenType Tok = ExpTok.Find(t); if (Tok == TTypeId) { char16 *v; if (!DoTypeId(Cur, v)) return false; Node &Var = n.New(); t = v; Cur--; DoVariableNode(Cur, Var, t); Var.ConstTok = TTypeId; Cur++; } else if (Tok == TStartRdBracket) { Cur++; auto &Next = n.New(); if (!Expression(Cur, Next.Child, Depth + 1)) return false; PrevIsOp = false; if (Next.Child.Length() > 0) Next.Tok = Next.Child[0].Tok; } else if (Tok == TEndRdBracket) { if (Depth > 0) Cur++; break; } else if (Tok == TComma || Tok == TSemiColon) { break; } else if (Depth == 0 && Tok == TEndSqBracket) { break; } else if (Tok == TTrue || Tok == TFalse || Tok == TNull) { n.New().SetConst(Cur++, Tok); } else { LOperator o = IsOp(t, PrevIsOp); if (o != OpNull) { // Operator PrevIsOp = 1; n.New().SetOp(o, Cur); } else { PrevIsOp = 0; LVariant m; m = t; LFunc *f = Methods.Find(m.Str()); LFunctionInfo *sf = 0; char16 *Next = GetTok(Cur+1); bool IsFunctionCall = !StricmpW(Next, sStartRdBracket); if (f && IsFunctionCall) { Node &Call = n.New(); Call.SetContextFunction(f, Cur++); // Now parse arguments // Get the start bracket if ((t = GetTok(Cur))) { if (StricmpW(t, sStartRdBracket) == 0) Cur++; else return OnError(Cur, "Function missing '('"); } else return OnError(Cur, "No token."); // Parse the args as expressions while ((t = GetTok(Cur))) { LTokenType Tok = ExpTok.Find(t); if (Tok == TComma) { // Do nothing... Cur++; } else if (Tok == TEndRdBracket) { break; } else if (Tok == TSemiColon) { return OnError(Cur, "Unexpected ';'"); } else if (!Expression(Cur, Call.Args.New())) { return OnError(Cur, "Can't parse function argument."); } } } else if ( (sf = Code->GetMethod(m.Str(), IsFunctionCall)) ) { Node &Call = n.New(); Call.SetScriptFunction(sf, Cur++); // Now parse arguments // Get the start bracket if ((t = GetTok(Cur))) { if (StricmpW(t, sStartRdBracket) == 0) Cur++; else return OnError(Cur, "Function missing '('"); } else return OnError(Cur, "No token."); // Parse the args as expressions while ((t = GetTok(Cur))) { if (StricmpW(t, sComma) == 0) { // Do nothing... Cur++; } else if (StricmpW(t, sEndRdBracket) == 0) { break; } else if (!Expression(Cur, Call.Args[Call.Args.Length()])) { return OnError(Cur, "Can't parse function argument."); } } } else if (IsAlpha(*t)) { // Variable... Node &Var = n.New(); if (!DoVariableNode(Cur, Var, t)) return false; } else if (*t == '\'' || *t == '\"' || LIsNumber(t)) { // Constant string or number n.New().SetConst(Cur, TLiteral); } else if (Tok == TStartCurlyBracket) { // List definition LArray Values; Cur++; int Index = 0; while ((t = Tokens[Cur])) { LTokenType Tok = ExpTok.Find(t); if (Tok == TComma) { Index++; } else if (Tok == TEndCurlyBracket) { break; } else if (*t == '\'' || *t == '\"' || LIsNumber(t)) { Values[Index] = Cur; } else { return OnError(Cur, "Unexpected token '%S' in list definition", t); } Cur++; } n.New().SetConst(Values, TLiteral); } else { // Unknown return OnError(Cur, "Unknown token '%S'", t); } } Cur++; } } return true; } /// Allocate a register (must be mirrored with DeallocReg) bool AllocReg(LVarRef &r, int LineOrTok, const char *file, int line) { for (int i=0; iPrint("CompileError:Register[%i] allocated by %s\n", n, a); } } #endif return false; } /// Deallocate a register bool DeallocReg(LVarRef &r) { if (r.Scope == SCOPE_REGISTER && r.Index >= 0) { int Bit = 1 << r.Index; // LAssert((Bit & Regs) != 0); Regs &= ~Bit; #ifdef _DEBUG RegAllocators[r.Index].Empty(); #endif } return true; } /// Count allocated registers int RegAllocCount() { int c = 0; for (int i=0; i &n) { LStringPipe e; for (unsigned i=0; iMethod.Get()); } else if (n[i].ScriptFunc) { e.Print("%s(...)", n[i].ScriptFunc->GetName()); } else { e.Print("#err#"); } } return e.NewStr(); } /// Creates byte code to evaluate an expression bool AsmExpression ( /// Where the result got stored LVarRef *Result, /// The nodes to create code for LArray &n, /// The depth of recursion int Depth = 0 ) { // Resolve any sub-expressions and store their values for (unsigned i = 0; i < n.Length(); i++) { auto &k = n[i]; if (!k.IsVar() && k.Child.Length()) { AllocReg(k.Reg, k.Tok, _FL); AsmExpression(&k.Reg, k.Child, Depth + 1); } } while (n.Length() > 1) { // Find which operator to handle first #ifdef _DEBUG size_t StartLength = n.Length(); #endif int OpIdx = -1; int Prec = -1; int Ops = 0; for (unsigned i=0; i Jmp; if (!TokenToVarRef(a, NullRef)) return OnError(a.Tok, "Can't convert left token to var ref."); if (Op == OpAnd) { // Jump over 'b' if 'a' is FALSE Jmp.Reset(new LJumpZero(this, a.Tok, a.Reg, false)); } else if (Op == OpOr) { // Jump over 'b' if 'a' is TRUE } if (!TokenToVarRef(b, LValue)) return OnError(b.Tok, "Can't convert right token to var ref."); LVarRef Reg; Reg.Empty(); if (a.Reg.Scope != SCOPE_REGISTER) { if (AllocReg(Reg, a.Tok, _FL)) { LAssert(Reg != a.Reg); Asm2(a.Tok, IAssign, Reg, a.Reg); a.Reg = Reg; } else return OnError(a.Tok, "Can't alloc register, Regs=0x%x", Regs); } Asm2(a.Tok, Op, a.Reg, b.Reg); if (Op == OpPlusEquals || Op == OpMinusEquals || Op == OpMulEquals || Op == OpDivEquals) { AssignVarRef(a, a.Reg); } } if (a.Reg != b.Reg) DeallocReg(b.Reg); n.DeleteAt(OpIdx+1, true); n.DeleteAt(OpIdx, true); // Result is in n[OpIdx-1] (ie the 'a' node) } else { LAssert(!"Not a valid type"); return OnError(n[0].Tok, "Not a valid type."); } #ifdef _DEBUG if (StartLength == n.Length()) { // No nodes removed... infinite loop! LAssert(!"No nodes removed."); return false; } #endif } if (n.Length() == 1) { auto &k = n[0]; if (!k.Reg.Valid()) { LVarRef *NullRef = NULL; if (!TokenToVarRef(k, NullRef)) { return false; } } if (Result) { if (Result->Valid() && *Result != k.Reg) DeallocReg(*Result); *Result = k.Reg; } else { DeallocReg(k.Reg); } return true; } return false; } /// Parses and assembles an expression bool DoExpression(uint32_t &Cur, LVarRef *Result) { LArray n; if (Expression(Cur, n)) { bool Status = AsmExpression(Result, n); return Status; } return false; } /// Converts a variable to it's type information bool DoTypeId(uint32_t &Cur, char16 *&Var) { if (GetTokType(Cur) != TTypeId) return OnError(Cur, "Expecting 'typeid'."); Cur++; if (GetTokType(Cur) != TStartRdBracket) return OnError(Cur, "Expecting '('."); Cur++; char16 *t = GetTok(Cur); if (!t || !IsAlpha(*t)) return OnError(Cur, "Expecting variable name."); Cur++; if (GetTokType(Cur) != TEndRdBracket) return OnError(Cur, "Expecting ')'."); Cur++; Var = t; return true; } /// Parses statements bool DoStatements(uint32_t &Cur, bool *LastWasReturn, bool MoreThanOne = true) { while (Cur < Tokens.Length()) { char16 *t = GetTok(Cur); if (!t) break; LTokenType Tok = ExpTok.Find(t); switch (Tok) { case TTypeId: { return OnError(Cur, "typeif only valid in an expression."); } case TSemiColon: { Cur++; break; } case TDebug: { Asm0(Cur++, IDebug); break; } case TBreakPoint: { Asm0(Cur++, IBreakPoint); break; } case TReturn: { Cur++; if (!DoReturn(Cur)) return false; if (LastWasReturn) *LastWasReturn = true; break; } case TEndCurlyBracket: case TFunction: { return true; } case TIf: { if (!DoIf(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } case TFor: { if (!DoFor(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } case TWhile: { if (!DoWhile(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } case TExtern: { if (!DoExtern(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } default: { if (!DoExpression(Cur, 0)) return false; LTokenType Tok = ExpTok.Find(GetTok(Cur)); if (Tok == TSemiColon) Cur++; if (LastWasReturn) *LastWasReturn = false; break; } } if (!MoreThanOne) break; } return true; } /// Parses if/else if/else construct bool DoIf(uint32_t &Cur) { Cur++; char16 *t; if (GetTokType(Cur) == TStartRdBracket) { Cur++; // Compile and asm code to evaluate the expression LVarRef Result; int ExpressionTok = Cur; Result.Empty(); if (DoExpression(Cur, &Result)) { t = GetTok(Cur); if (!t || StricmpW(t, sEndRdBracket)) return OnError(Cur, "if missing ')'."); Cur++; t = GetTok(Cur); if (!t) return OnError(Cur, "if missing body statement."); // Output the jump instruction LAutoPtr Jmp(new LJumpZero(this, ExpressionTok, Result)); if (!StricmpW(t, sStartCurlyBracket)) { // Statement block Cur++; while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } else { // Single statement if (!DoStatements(Cur, NULL, false)) return false; } // Check for else... if ((t = GetTok(Cur)) && StricmpW(t, sElse) == 0) { // Add a jump for the "true" part of the expression to // jump over the "else" part. Asm0(Cur, IJump); size_t JOffset = Code->ByteCode.Length(); if (Code->ByteCode.Length(JOffset + 4)) { // Initialize the ptr to zero int32 *Ptr = (int32*)&Code->ByteCode[JOffset]; *Ptr = 0; // Resolve jz to here... Jmp.Reset(); // Compile the else block Cur++; if ((t = GetTok(Cur)) && StricmpW(t, sStartCurlyBracket) == 0) { // 'Else' Statement block Cur++; while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } else { // Single statement if (!DoStatements(Cur, NULL, false)) return false; } // Resolve the "JOffset" jump that takes execution of // the 'true' part over the 'else' part Ptr = (int32*)&Code->ByteCode[JOffset]; *Ptr = (int32) (Code->ByteCode.Length() - JOffset - sizeof(int32)); if (*Ptr == 0) { // Empty statement... so delete the Jump instruction Code->ByteCode.Length(JOffset - 1); } } else OnError(Cur, "Mem alloc"); } return true; } } else return OnError(Cur, "if missing '('"); return false; } LArray &GetByteCode() { return Code->ByteCode; } class LJumpZero { LCompilerPriv *Comp; int JzOffset; public: LJumpZero(LCompilerPriv *d, int Tok, LVarRef &r, bool DeallocReg = true) { // Create jump instruction to jump over the body if the expression evaluates to false Comp = d; Comp->Asm1(Tok, IJumpZero, r); if (DeallocReg) Comp->DeallocReg(r); JzOffset = (int) Comp->GetByteCode().Length(); Comp->GetByteCode().Length(JzOffset + sizeof(int32)); } ~LJumpZero() { // Resolve jump int32 *Ptr = (int32*) &Comp->GetByteCode()[JzOffset]; *Ptr = (int32) (Comp->GetByteCode().Length() - (JzOffset + sizeof(int32))); } }; /// Parses while construct bool DoWhile(uint32_t &Cur) { Cur++; char16 *t = GetTok(Cur); if (!t || StricmpW(t, sStartRdBracket)) return OnError(Cur, "Expecting '(' after 'while'"); Cur++; // Store start of condition code size_t ConditionStart = Code->ByteCode.Length(); // Compile condition evalulation LVarRef r; r.Empty(); if (!DoExpression(Cur, &r)) return false; // Create jump instruction to jump over the body if the expression evaluates to false { LJumpZero Jump(this, Cur, r); if (!(t = GetTok(Cur)) || StricmpW(t, sEndRdBracket)) return OnError(Cur, "Expecting ')'"); Cur++; // Compile the body of the loop if (!(t = GetTok(Cur))) return OnError(Cur, "Unexpected eof."); Cur++; if (StricmpW(t, sStartCurlyBracket) == 0) { // Block while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } else { // Single statement DoStatements(Cur, NULL, false); } // Jump to condition evaluation at 'ConditionStart' Asm0(Cur, IJump); size_t JOffset = Code->ByteCode.Length(); Code->ByteCode.Length(JOffset + sizeof(int32)); int32 *Ptr = (int32*) &Code->ByteCode[JOffset]; *Ptr = (int32) (ConditionStart - Code->ByteCode.Length()); } return true; } /// Parses for construct bool DoFor(uint32_t &Cur) { /* For loop asm structure: +---------------------------+ | Pre-condition expression | +---------------------------+ | Eval loop contdition exp. |<--+ | | | | JUMP ZERO (jumpz) |---+-+ +---------------------------+ | | | Body of loop... | | | | | | | | | | | | | | | | | | | | | | | +---------------------------+ | | | Post-cond. expression | | | | | | | | JUMP |---+ | +---------------------------+ | | Following code... |<----+ . . */ Cur++; char16 *t = GetTok(Cur); if (!t || StricmpW(t, sStartRdBracket)) return OnError(Cur, "Expecting '(' after 'for'"); Cur++; // Compile initial statement LVarRef r; r.Empty(); t = GetTok(Cur); if (!t) return false; if (StricmpW(t, sSemiColon) && !DoExpression(Cur, 0)) return false; t = GetTok(Cur); // Look for ';' if (!t || StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';'"); Cur++; // Store start of condition code size_t ConditionStart = Code->ByteCode.Length(); // Compile condition evalulation if (!DoExpression(Cur, &r)) return false; { LJumpZero Jmp(this, Cur, r); t = GetTok(Cur); // Look for ';' if (!t || StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';'"); Cur++; // Compile the post expression code size_t PostCodeStart = Code->ByteCode.Length(); t = GetTok(Cur); if (StricmpW(t, sEndRdBracket) && !DoExpression(Cur, 0)) return false; // Store post expression code in temp variable LArray PostCode; size_t PostCodeLen = Code->ByteCode.Length() - PostCodeStart; if (PostCodeLen) { PostCode.Length(PostCodeLen); memcpy(&PostCode[0], &Code->ByteCode[PostCodeStart], PostCodeLen); // Delete the post expression off the byte code, we are putting it after the block code Code->ByteCode.Length(PostCodeStart); } // Look for ')' t = GetTok(Cur); if (!t || StricmpW(t, sEndRdBracket)) return OnError(Cur, "Expecting ')'"); Cur++; // Compile body of loop if ((t = GetTok(Cur)) && StricmpW(t, sStartCurlyBracket) == 0) { Cur++; while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } // Add post expression code if (PostCodeLen) { size_t Len = Code->ByteCode.Length(); Code->ByteCode.Length(Len + PostCodeLen); memcpy(&Code->ByteCode[Len], &PostCode[0], PostCodeLen); } // Jump to condition evaluation at 'ConditionStart' Asm0(Cur, IJump); size_t JOffset = Code->ByteCode.Length(); Code->ByteCode.Length(JOffset + sizeof(int32)); int32 *Ptr = (int32*) &Code->ByteCode[JOffset]; *Ptr = (int32) (ConditionStart - Code->ByteCode.Length()); } return true; } /// Compiles return construct bool DoReturn(uint32_t &Cur) { LVarRef ReturnValue; char16 *t; int StartTok = Cur; ReturnValue.Empty(); LArray Exp; if (!Expression(Cur, Exp)) { return OnError(Cur, "Failed to compile return expression."); } else if (!AsmExpression(&ReturnValue, Exp)) { return OnError(Cur, "Failed to assemble return expression."); } if (!(t = GetTok(Cur)) || StricmpW(t, sSemiColon)) { return OnError(Cur, "Expecting ';' after return expression."); } Asm1(StartTok, IRet, ReturnValue); DeallocReg(ReturnValue); Cur++; return true; } // Compile a method definition bool DoFunction ( /// Cursor token index uint32_t &Cur, /// [Optional] Current struct / class. /// If not NULL this is a method definition for said class. LCustomType *Struct = NULL ) { bool Status = false; bool LastInstIsReturn = false; if (!JumpLoc) { size_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 5)) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = IJump; *p.i32++ = 0; JumpLoc = Len + 1; } else OnError(Cur, "Mem alloc failed."); } LString FunctionName; LCustomType::Method *StructMethod = NULL; // Member function of script struct/class LFunctionInfo *ScriptMethod = NULL; // Standalone scripting function char16 *Name = GetTok(Cur); if (Name) { Cur++; char16 *t = GetTok(Cur); if (!t || StricmpW(t, sStartRdBracket)) return OnError(Cur, "Expecting '(' in function."); FunctionName = Name; // Parse parameters LArray Params; Cur++; while ((t = GetTok(Cur))) { if (IsAlpha(*t)) { Params.New() = t; Cur++; if (!(t = GetTok(Cur))) goto UnexpectedFuncEof; } if (!StricmpW(t, sComma)) ; else if (!StricmpW(t, sEndRdBracket)) { Cur++; break; } else goto UnexpectedFuncEof; Cur++; } if (Struct) { StructMethod = Struct->DefineMethod(FunctionName, Params, Code->ByteCode.Length()); } else { ScriptMethod = Code->GetMethod(FunctionName, true); if (!ScriptMethod) return OnError(Cur, "Can't define method '%s'.", FunctionName.Get()); ScriptMethod->GetParams() = Params; ScriptMethod->SetStartAddr((int32_t)Code->ByteCode.Length()); } // Parse start of body if (!(t = GetTok(Cur))) goto UnexpectedFuncEof; if (StricmpW(t, sStartCurlyBracket)) return OnError(Cur, "Expecting '{'."); // Setup new scope(s) LAutoPtr ObjectScope; if (Struct) { // The object scope has to be first so that local variables take // precedence over object member variables. if (ObjectScope.Reset(new LVariables(Struct))) Scopes.Add(ObjectScope); } LVariables LocalScope(SCOPE_LOCAL); if (Struct) LocalScope.Var("This", true); for (unsigned i=0; iFrameSize = (uint16)LocalScope.Length(); else if (ScriptMethod) ScriptMethod->SetFrameSize(LocalScope.Length()); else LAssert(!"What are you defining exactly?"); Status = true; Cur++; // LgiTrace("Added method %s @ %i, stack=%i, args=%i\n", f->Name.Str(), f->StartAddr, f->FrameSize, f->Params.Length()); break; } // Parse statement in the body if (!DoStatements(Cur, &LastInstIsReturn)) return OnError(Cur, "Can't compile function body."); } // Remove any scopes we created Scopes.PopLast(); if (Struct) Scopes.PopLast(); } if (!LastInstIsReturn) { LVarRef RetVal; AllocNull(RetVal); Asm1(Cur, IRet, RetVal); } return Status; UnexpectedFuncEof: return OnError(Cur, "Unexpected EOF in function."); } LExternFunc::ExternType ParseExternType(LArray &Strs) { LExternFunc::ExternType Type; Type.Out = false; Type.Ptr = 0; Type.ArrayLen = 1; Type.Base = GV_NULL; Type.Unsigned = false; bool InArray = false; for (unsigned i=0; i Tok; const char16 *t; while ((t = GetTok(Cur))) { if (!StricmpW(t, sStartRdBracket)) { Cur++; break; } else Tok.Add(t); Cur++; } if (Tok.Length() < 3) { return OnError(Cur, "Not enough tokens in extern decl."); } // First token is library name LExternFunc *e = new LExternFunc; if (!e) return OnError(Cur, "Alloc error."); Code->Externs.Add(e); e->Type = ExternFunc; char16 sQuotes[] = {'\'','\"',0}; LAutoWString LibName(TrimStrW(Tok[0], sQuotes)); e->Lib.Reset(WideToUtf8(LibName)); Tok.DeleteAt(0, true); e->Method = Tok.Last(); Tok.DeleteAt(Tok.Length()-1, true); if (!ValidStr(e->Method)) { Code->Externs.Length(Code->Externs.Length()-1); return OnError(Cur, "Failed to get extern method name."); } // Parse return type e->ReturnType = ParseExternType(Tok); // Parse argument types Tok.Length(0); while ((t = GetTok(Cur))) { if (!StricmpW(t, sEndRdBracket)) { e->ArgType.New() = ParseExternType(Tok); Cur++; break; } else if (!StricmpW(t, sComma)) { e->ArgType.New() = ParseExternType(Tok); } else Tok.Add(t); Cur++; } if ((t = GetTok(Cur))) { if (StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';' in extern decl."); Cur++; } Methods.Add(e->Method, e); return true; } struct ExpPart { LOperator Op; int Value; ExpPart() { Op = OpNull; Value = 0; } }; int Evaluate(LArray &Exp, size_t Start, size_t End) { LArray p; // Find outer brackets ssize_t e; for (e = End; e >= (ssize_t)Start; e--) { if (Exp[e][0] == ')') break; } for (size_t i = Start; i <= End; i++) { char16 *t = Exp[i]; if (*t == '(') { p.New().Value = Evaluate(Exp, i + 1, e - 1); i = e; } else { LOperator op = IsOp(t, 0); if (op) { p.New().Op = op; } else if (IsDigit(*t)) { p.New().Value = AtoiW(t); } else { LAssert(0); break; } } } while (p.Length() > 1) { int HighPrec = 32; int Idx = -1; for (unsigned i=0; i 0 && Idx < (int)p.Length() - 1) { switch (p[Idx].Op) { case OpPlus: p[Idx-1].Value += p[Idx+1].Value; break; case OpMinus: p[Idx-1].Value -= p[Idx+1].Value; break; case OpMul: p[Idx-1].Value *= p[Idx+1].Value; break; case OpDiv: if (p[Idx+1].Value) p[Idx-1].Value /= p[Idx+1].Value; else LAssert(!"Div 0"); break; default: LAssert(!"Impl me."); break; } p.DeleteAt(Idx, true); p.DeleteAt(Idx, true); } else { LAssert(0); break; } } else { LAssert(!"Impl me."); } } if (p.Length() == 1) { LAssert(p[0].Op == OpNull); return p[0].Value; } LAssert(0); return 0; } int ByteSizeFromType(char *s, LVariantType t) { switch (t) { case GV_INT32: { char n[16], *o = n; for (char *c = s; *c; c++) { if (IsDigit(*c) && o < n + sizeof(n) - 1) *o++ = *c; } *o++ = 0; int bits = ::atoi(n); switch (bits) { case 8: return 1; case 16: return 2; } return 4; break; } case GV_INT64: { return 8; } case GV_WSTRING: { return sizeof(char16); } default: break; } return 1; } /// Compiles struct construct bool DoStruct(uint32_t &Cur) { bool Status = false; // Parse struct name and setup a type char16 *t; LCustomType *Def = Code->Types.Find(t = GetTok(Cur)); if (!Def) Code->Types.Add(t, Def = new LCustomType(t)); Cur++; t = GetTok(Cur); if (!t || StricmpW(t, sStartCurlyBracket)) return OnError(Cur, "Expecting '{'"); Cur++; // Parse members while ((t = GetTok(Cur))) { // End of type def? LTokenType tt = ExpTok.Find(t); if (tt == TEndCurlyBracket) { Cur++; tt = GetTokType(Cur); if (!tt || tt != TSemiColon) return OnError(Cur, "Expecting ';' after '}'"); Status = true; break; } if (tt == TFunction) { Cur++; if (!DoFunction(Cur, Def)) return false; } else { // Parse member field LVariant TypeName = t; LCustomType *NestedType = 0; LVariantType Type = Types.Find(TypeName.Str()); if (!Type) { // Check other custom types NestedType = Code->GetType(t); if (!NestedType) return OnError(Cur, "Unknown type '%S' in struct definition.", t); // Ok, nested type. Type = GV_CUSTOM; } Cur++; if (!(t = GetTok(Cur))) goto EofError; bool Pointer = false; if (t[0] == '*' && t[1] == 0) { Pointer = true; Cur++; if (!(t = GetTok(Cur))) goto EofError; } LVariant Name = t; Cur++; if (!(t = GetTok(Cur))) goto EofError; int Array = 1; if (!StricmpW(t, sStartSqBracket)) { // Array Cur++; LArray Exp; while ((t = GetTok(Cur))) { Cur++; if (!StricmpW(t, sEndSqBracket)) break; Exp.Add(t); } Array = Evaluate(Exp, 0, Exp.Length()-1); } int MemberAddr = Def->AddressOf(Name.Str()); if (MemberAddr >= 0) return OnError(Cur, "Member '%s' can't be defined twice.", Name.Str()); if (NestedType) { if (!Def->DefineField(Name.Str(), NestedType, Array)) return OnError(Cur, "Failed to define field '%s'.", Name.Str()); } else { int Bytes = ByteSizeFromType(TypeName.Str(), Type); if (!Def->DefineField(Name.Str(), Type, Bytes, Array)) return OnError(Cur, "Failed to define field '%s'.", Name.Str()); } t = GetTok(Cur); if (StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';'"); Cur++; } } return Status; EofError: return OnError(Cur, "Unexpected EOF."); } /// Compiler entry point bool Compile() { uint32_t Cur = 0; JumpLoc = 0; // Setup the global scope Scopes.Length(0); Scopes.Add(&Code->Globals); // Compile the code... while (Cur < Tokens.Length()) { char16 *t = GetTok(Cur); if (!t) break; if (*t == '#' || StricmpW(t, sSemiColon) == 0) { Cur++; } else if (!StricmpW(t, sFunction)) { if (!DoFunction(++Cur)) return false; } else if (!StricmpW(t, sStruct)) { if (!DoStruct(++Cur)) return false; } else if (!StricmpW(t, sEndCurlyBracket)) { return OnError(Cur, "Not expecting '}'."); } else if (!StricmpW(t, sExtern)) { if (!DoExtern(++Cur)) return false; } else { if (JumpLoc) { LScriptPtr p; p.u8 = &Code->ByteCode[JumpLoc]; *p.u32 = (uint32_t) (Code->ByteCode.Length() - (JumpLoc + 4)); JumpLoc = 0; } if (!DoStatements(Cur, NULL)) { return OnError(Cur, "Statement compilation failed."); } } } if (JumpLoc) { LScriptPtr p; p.u8 = &Code->ByteCode[JumpLoc]; *p.u32 = (uint32_t) (Code->ByteCode.Length() - (JumpLoc + 4)); JumpLoc = 0; } // Do link time fix ups... for (unsigned i=0; iValidStartAddr()) { if (f.Args != f.Func->GetParams().Length()) { return OnError(f.Tok, "Function call '%s' has wrong arg count (caller=%i, method=%i).", f.Func->GetName(), f.Args, f.Func->GetParams().Length()); } else if (!f.Func->ValidFrameSize()) { return OnError(f.Tok, "Function call '%s' has no frame size.", f.Func->GetName()); } else { LScriptPtr p; p.u8 = &Code->ByteCode[f.Offset]; LAssert(*p.u32 == 0); *p.u32++ = f.Func->GetStartAddr(); *p.u16++ = f.Func->GetFrameSize(); } } else { return OnError(f.Tok, "Function '%s' not defined.", f.Func->GetName()); } } Fixups.Length(0); return true; } }; LCompiler::LCompiler() { d = new LCompilerPriv; } LCompiler::~LCompiler() { DeleteObj(d); } bool LCompiler::Compile ( LAutoPtr &Code, LScriptContext *SysContext, LScriptContext *UserContext, const char *FileName, const char *Script, LDom *Args ) { if (!Script) return false; LStringPipe p; #ifdef DEBUG_SCRIPT_FILE d->Debug = Stristr(FileName, DEBUG_SCRIPT_FILE) != NULL; #endif if (SysContext && SysContext->GetLog()) d->Log = SysContext->GetLog(); else if (UserContext && UserContext->GetLog()) d->Log = UserContext->GetLog(); else d->Log = &p; d->Methods.Empty(); if (SysContext) { - LHostFunc *f = SysContext->GetCommands(); + auto f = SysContext->GetCommands(); for (int i=0; f[i].Method; i++) { f[i].Context = SysContext; d->Methods.Add(f[i].Method, &f[i]); } } d->SysCtx = SysContext; d->UserCtx = UserContext; if (d->UserCtx) { - LHostFunc *f = d->UserCtx->GetCommands(); + auto f = d->UserCtx->GetCommands(); if (f) { for (int i=0; f[i].Method; i++) { f[i].Context = d->UserCtx; if (!d->Methods.Find(f[i].Method)) d->Methods.Add(f[i].Method, f+i); else { LgiTrace("%s:%i - Conflicting name of method in application's context: '%s'\n", _FL, f[i].Method.Get()); LAssert(!"Conflicting name of method in application's context."); } } } } if (!Code) Code.Reset(new LCompiledCode); bool Status = false; d->Code = dynamic_cast(Code.Get()); if (d->Code) { d->Code->UserContext = UserContext; d->Code->SysContext = SysContext; d->Code->SetSource(FileName, Script); bool LexResult = d->Lex((char*)Script, FileName); if (LexResult) { d->ScriptArgs = Args; Status = d->Compile(); } else { d->OnError(0, "Failed to lex script.\n"); } } else { d->OnError(0, "Allocation failed.\n"); } d->Code = NULL; return Status; } ////////////////////////////////////////////////////////////////////// class LScriptEnginePrivate { public: - LViewI *Parent; + LViewI *Parent = NULL; SystemFunctions SysContext; - LScriptContext *UserContext; - LCompiledCode *Code; - LVmCallback *Callback; + LScriptContext *UserContext = NULL; + LCompiledCode *Code = NULL; + LVmCallback *Callback = NULL; LVariant ReturnValue; - - LScriptEnginePrivate() - { - UserContext = NULL; - Parent = NULL; - Code = NULL; - Callback = NULL; - } }; LScriptEngine::LScriptEngine(LViewI *parent, LScriptContext *UserContext, LVmCallback *Callback) { d = new LScriptEnginePrivate; d->Parent = parent; d->UserContext = UserContext; d->Callback = Callback; d->SysContext.SetEngine(this); } LScriptEngine::~LScriptEngine() { DeleteObj(d); } LCompiledCode *LScriptEngine::GetCurrentCode() { return d->Code; } bool LScriptEngine::Compile(LAutoPtr &Obj, LScriptContext *UserContext, const char *Script, const char *FileName, LDom *Args) { if (!Script) { LAssert(!"Param error"); return NULL; } LCompiler Comp; return Comp.Compile(Obj, &d->SysContext, UserContext ? UserContext : d->UserContext, FileName, Script, Args); } LExecutionStatus LScriptEngine::Run(LCompiledCode *Obj, LVariant *Ret, const char *TempPath) { LExecutionStatus Status = ScriptError; d->Code = Obj; if (d->Code) { LVirtualMachine Vm(d->Callback); if (TempPath) Vm.SetTempPath(TempPath); Status = Vm.Execute(d->Code, 0, NULL, true, Ret ? Ret : &d->ReturnValue); d->Code = NULL; } return Status; } LExecutionStatus LScriptEngine::RunTemporary(LCompiledCode *Obj, char *Script, LVariant *Ret) { LExecutionStatus Status = ScriptError; LCompiledCode *Code = dynamic_cast(Obj); if (Script && Code) { LAutoPtr Temp(new LCompiledCode(*Code)); uint32_t TempLen = (uint32_t) Temp->Length(); d->Code = Temp; LCompiler Comp; if (Comp.Compile(Temp, &d->SysContext, d->UserContext, Temp->GetFileName(), Script, NULL)) { LVirtualMachine Vm(d->Callback); Status = Vm.Execute(dynamic_cast(Temp.Get()), TempLen, NULL, true, Ret ? Ret : &d->ReturnValue); } d->Code = NULL; } return Status; } bool LScriptEngine::EvaluateExpression(LVariant *Result, LDom *VariableSource, const char *Expression) { if (!Result || !VariableSource || !Expression) { LAssert(!"Param error"); return false; } // Create trivial script to evaluate the expression LString a; a.Printf("return %s;", Expression); // Compile the script LCompiler Comp; LAutoPtr Obj; if (!Comp.Compile(Obj, &d->SysContext, d->UserContext, NULL, a, VariableSource)) { LAssert(0); return false; } // Execute the script LVirtualMachine Vm(d->Callback); LCompiledCode *Code = dynamic_cast(Obj.Get()); auto ReturnVal = Result ? Result : &d->ReturnValue; LExecutionStatus s = Vm.Execute(Code, 0, NULL, true, ReturnVal); if (s != ScriptSuccess) { return false; } if (ReturnVal->Type == GV_INT64 && !(ReturnVal->Value.Int64 >> 32)) { *ReturnVal = ReturnVal->CastInt32(); } return true; } LStream *LScriptEngine::GetConsole() { if (d->SysContext.GetLog()) return d->SysContext.GetLog(); if (d->UserContext && d->UserContext->GetLog()) return d->UserContext->GetLog(); return NULL; } bool LScriptEngine::SetConsole(LStream *t) { d->SysContext.SetLog(t); if (d->UserContext) d->UserContext->SetLog(t); return true; } bool LScriptEngine::CallMethod(LCompiledCode *Obj, const char *Method, LScriptArguments &Args) { LCompiledCode *Code = dynamic_cast(Obj); if (!Code || !Method) return false; LFunctionInfo *i = Code->GetMethod(Method); if (!i) return false; LVirtualMachine Vm(d->Callback); Args.Vm = &Vm; LExecutionStatus Status = Vm.ExecuteFunction(Code, i, Args, NULL); Args.Vm = NULL; return Status != ScriptError; } LScriptContext *LScriptEngine::GetSystemContext() { return &d->SysContext; } diff --git a/src/common/Coding/ScriptLibrary.cpp b/src/common/Coding/ScriptLibrary.cpp --- a/src/common/Coding/ScriptLibrary.cpp +++ b/src/common/Coding/ScriptLibrary.cpp @@ -1,1262 +1,1262 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/SubProcess.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "ScriptingPriv.h" ////////////////////////////////////////////////////////////////////////////////////// char16 sChar[] = L"char"; char16 sInt[] = { 'i','n','t', 0 }; char16 sUInt[] = { 'u','i','n','t', 0 }; char16 sInt32[] = { 'i','n','t','3','2', 0 }; char16 sUInt32[] = { 'u','i','n','t','3','2', 0 }; char16 sInt64[] = { 'i','n','t','6','4', 0 }; char16 sHWND[] = { 'H','W','N','D', 0 }; char16 sDWORD[] = { 'D','W','O','R','D', 0 }; char16 sLPTSTR[] = { 's','L','P','T','S','T','R', 0 }; char16 sLPCTSTR[] = { 's','L','P','C','T','S','T','R', 0 }; char16 sElse[] = { 'e','l','s','e', 0 }; char16 sIf[] = { 'i','f',0 }; char16 sFunction[] = { 'f','u','n','c','t','i','o','n',0 }; char16 sExtern[] = { 'e','x','t','e','r','n',0 }; char16 sFor[] = { 'f','o','r',0 }; char16 sWhile[] = { 'w','h','i','l','e',0 }; char16 sReturn[] = { 'r','e','t','u','r','n',0 }; char16 sInclude[] = { 'i','n','c','l','u','d','e',0 }; char16 sDefine[] = { 'd','e','f','i','n','e',0 }; char16 sStruct[] = { 's','t','r','u','c','t',0 }; char16 sTrue[] = { 't','r','u','e',0 }; char16 sFalse[] = { 'f','a','l','s','e',0 }; char16 sNull[] = { 'n','u','l','l',0 }; char16 sOutParam[] = { '_','o','u','t','_',0}; char16 sHash[] = { '#', 0 }; char16 sPeriod[] = { '.', 0 }; char16 sComma[] = { ',', 0 }; char16 sSemiColon[] = { ';', 0 }; char16 sStartRdBracket[] = { '(', 0 }; char16 sEndRdBracket[] = { ')', 0 }; char16 sStartSqBracket[] = { '[', 0 }; char16 sEndSqBracket[] = { ']', 0 }; char16 sStartCurlyBracket[] = { '{', 0 }; char16 sEndCurlyBracket[] = { '}', 0 }; ////////////////////////////////////////////////////////////////////////////////////// LExecutionStatus LHostFunc::Call(LScriptContext *Ctx, LScriptArguments &Args) { return (Ctx->*(Func))(Args) ? ScriptSuccess : ScriptError; } -const char *InstToString(GInstruction i) +const char *InstToString(LInstruction i) { #undef _i #define _i(name, opcode, desc) \ case name: return desc; switch (i) { AllInstructions } return "#err"; } ////////////////////////////////////////////////////////////////////////////////////// int LScriptUtils::atoi(char16 *s) { int i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::atoi(b); } return i; } int64 LScriptUtils::atoi64(char16 *s) { int64 i = 0; if (s) { #ifdef _MSC_VER i = _wtoi64(s); #else char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = strtoll(b, 0, 10); #endif } return i; } double LScriptUtils::atof(char16 *s) { double i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::atof(b); } return i; } int LScriptUtils::htoi(char16 *s) { int i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::htoi(b); } return i; } ////////////////////////////////////////////////////////////////////////////////////// SystemFunctions::SystemFunctions() { Engine = NULL; Log = NULL; #ifdef WINNATIVE Brk = NULL; #endif } SystemFunctions::~SystemFunctions() { } LStream *SystemFunctions::GetLog() { return Log; } bool SystemFunctions::SetLog(LStream *log) { LAssert(Log == NULL); Log = log; return true; } void SystemFunctions::SetEngine(LScriptEngine *Eng) { Engine = Eng; } bool SystemFunctions::Assert(LScriptArguments &Args) { *Args.GetReturn() = true; if (Args.Length() == 0) return true; auto v = Args[0]->CastInt32(); if (!v) { const char *Msg = Args.Length() > 1 ? Args[1]->CastString() : NULL; *Args.GetReturn() = false; Args.Throw(NULL, -1, Msg); } return true; } bool SystemFunctions::DebuggerEnabled(LScriptArguments &Args) { if (Args.Length() == 0) { LAssert(!"Wrong args."); return false; } Args.GetVm()->SetDebuggerEnabled(Args[0]->CastInt32() != 0); return true; } bool SystemFunctions::Throw(LScriptArguments &Args) { const char *Msg = Args.Length() > 0 ? Args[0]->CastString() : NULL; Args.Throw(NULL, -1, Msg); return true; } bool SystemFunctions::LoadString(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } *Args.GetReturn() = LLoadString(Args[0]->CastInt32()); return true; } bool SystemFunctions::Sprintf(LScriptArguments &Args) { if (Args.Length() < 1) { LAssert(!"Wrong args."); return false; } char *Fmt = Args[0]->Str(); if (!Fmt) return false; #if defined(LINUX) || defined(MAC) || defined(HAIKU) // No support for sprintf with generated args... hack a string up // Formatting widths etc not supported. LArray s; int i = 1; for (char *f = Fmt; *f; f++) { if (f[0] == '%' && f[1] != '%') { f++; // Skip '%' // char *Fmt = f; while (*f && !IsAlpha(*f)) f++; // Skip formatting.. if (i >= Args.Length()) break; // No more arguments... switch (*f) { case 's': { // String... char *v = Args[i]->CastString(); if (v) s.Add(v, strlen(v)); else s.Add((char*)"(null)", 4); break; } case 'c': { char *Str = Args[i]->Str(); s.Add(Str ? *Str : '?'); break; } case 'f': case 'g': { break; } case 'u': case 'd': case 'i': { // Int... LString v; v.Printf("%i", Args[i]->CastInt32()); s.Add(v.Get(), v.Length()); break; } } i++; } else s.Add(*f); } s.Add(0); // NULL terminate *Args.GetReturn() = s.AddressOf(); #else LArray Params; va_list a; unsigned i = 1; for (char *f = Fmt; *f; f++) { if (f[0] == '%' && f[1] != '%') { char *t = f + 1; while (*t && !IsAlpha(*t)) t++; if (i >= Args.Length()) { LAssert(!"Not enough args."); break; } switch (*t) { case 's': { Params.Add((UNativeInt)Args[i++]->Str()); break; } case 'c': { char *Str = Args[i++]->Str(); Params.Add(Str ? *Str : '?'); break; } case 'f': case 'g': { union tmp { double Dbl; struct { uint32_t High; uint32_t Low; }; } Tmp; Tmp.Dbl = Args[i++]->CastDouble(); Params.Add(Tmp.High); Params.Add(Tmp.Low); break; } default: { Params.Add(Args[i++]->CastInt32()); break; } } f = *t ? t + 1 : t; } } a = (va_list) &Params[0]; #ifndef WIN32 #define _vsnprintf vsnprintf #endif char Buf[1024]; vsprintf_s(Buf, sizeof(Buf), Fmt, a); *Args.GetReturn() = Buf; #endif return true; } bool SystemFunctions::ReadTextFile(LScriptArguments &Args) { if (Args.Length() == 1 && LFileExists(Args[0]->CastString())) { LFile f(Args[0]->CastString(), O_READ); if (!f) return false; auto sz = f.GetSize(); if (sz < 0) return false; char *txt = new char[sz+1]; if (!txt) return false; auto rd = f.Read(txt, sz); if (rd >= 0) { txt[rd] = 0; Args.GetReturn()->OwnStr(txt); } delete [] txt; return rd == sz; } return false; } bool SystemFunctions::WriteTextFile(LScriptArguments &Args) { if (Args.Length() == 2) { LFile f; if (f.Open(Args[0]->CastString(), O_WRITE)) { f.SetSize(0); LVariant *v = Args[1]; if (v) { switch (v->Type) { default: break; case GV_STRING: { size_t Len = strlen(v->Value.String); *Args.GetReturn() = f.Write(v->Value.String, Len) == Len; return true; break; } case GV_BINARY: { *Args.GetReturn() = f.Write(v->Value.Binary.Data, v->Value.Binary.Length) == v->Value.Binary.Length; return true; break; } } } } } return false; } LView *SystemFunctions::CastLView(LVariant &v) { switch (v.Type) { default: break; case GV_DOM: return dynamic_cast(v.Value.Dom); case GV_LVIEW: return v.Value.View; } return 0; } bool SystemFunctions::SelectFiles(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { Args.Throw(_FL, "SelectFiles(Parent, Callback[, FileTypes[, InitialDir[, MultiSelect[, SaveAs]]]]) expects at least 2 arguments."); return false; } auto Vm = dynamic_cast(Args.GetVm()); if (!Vm) return false; auto Ctx = Vm->SaveContext(); if (!Ctx) { Args.Throw(_FL, "SelectFiles(...) requires a valid callback context."); return false; } LFileSelect *s = new LFileSelect; if (!s) return false; s->Parent(CastLView(*Args[0])); auto Callback = Args[1]->Str(); auto Types = LString(Args.IdxCheck(2) ? Args[2]->CastString() : NULL).SplitDelimit(",;:"); for (auto c: Types) { char *sp = strrchr(c, ' '); if (sp) { *sp++ = 0; s->Type(sp, c); } else { char *dot = strrchr(c, '.'); if (dot) { char Type[256]; sprintf_s(Type, sizeof(Type), "%s files", dot + 1); s->Type(Type, c); } } } s->Type("All Files", LGI_ALL_FILES); s->InitialDir (Args.IdxCheck(3) ? Args[3]->CastString() : 0); s->MultiSelect(Args.IdxCheck(4) ? Args[4]->CastInt32() != 0 : true); bool SaveAs = Args.IdxCheck(5) ? Args[5]->CastInt32() != 0 : false; auto Process = [Callback=LString(Callback),Ctx](LFileSelect *s, bool ok) { if (ok) { LScriptArguments Args(NULL); LVariant *v = new LVariant; if (auto Lst = v->SetList()) { for (unsigned i=0; iLength(); i++) { auto path = (*s)[i]; Lst->Insert(new LVariant(path)); } } Args.Add(v); Ctx.Call(Callback, Args); Args.DeleteObjects(); } delete s; }; if (SaveAs) s->Save(Process); else s->Open(Process); return true; } bool SystemFunctions::SelectFolder(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { Args.Throw(_FL, "SelectFolder(Parent, Callback[, InitialDir]) expects at least 2 arguments."); return false; } auto Vm = dynamic_cast(Args.GetVm()); if (!Vm) return false; auto Ctx = Vm->SaveContext(); if (!Ctx) { Args.Throw(_FL, "SelectFiles(...) requires a valid callback context."); return false; } LFileSelect *s = new LFileSelect; if (!s) return false; s->Parent(CastLView(*Args[0])); auto Callback = Args[1]->Str(); if (Args.IdxCheck(2)) s->InitialDir(Args[2]->CastString()); s->OpenFolder([Ctx, Callback = LString(Callback)](auto s, bool ok) { if (ok) { LScriptArguments Args(NULL); Args.Add(new LVariant(s->Name())); Ctx.Call(Callback, Args); Args.DeleteObjects(); } delete s; }); return true; } bool SystemFunctions::Sleep(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } LSleep(Args[0]->CastInt32()); return true; } bool SystemFunctions::ToString(LScriptArguments &Args) { LStringPipe p; const char *Sep = ", "; for (unsigned i=0; iToString(); p.Print("%s%s", i?Sep:"", s.Get()); } Args.GetReturn()->OwnStr(p.NewStr()); return true; } bool SystemFunctions::Lgi4CC(LScriptArguments &Args) { auto s = Args.StringAt(0); if (!s) return false; auto i = ::Lgi4CC(s); *Args.GetReturn() = i; return true; } bool SystemFunctions::Print(LScriptArguments &Args) { LStream *Out = Log ? Log : (Engine ? Engine->GetConsole() : NULL); for (unsigned n=0; Out && nPrint("%s", v.ToString().Get()); break; } default: { auto f = v.CastString(); if (f) { size_t Len = strlen(f); Out->Write(f, Len); } else { Out->Write("NULL", 4); } break; } } } return true; } bool SystemFunctions::FormatSize(LScriptArguments &Args) { if (Args.Length() != 1) return false; char s[64]; LFormatSize(s, sizeof(s), Args[0]->CastInt64()); *Args.GetReturn() = s; return true; } bool SystemFunctions::ClockTick(LScriptArguments &Args) { *Args.GetReturn() = (int64)LCurrentTime(); return true; } bool SystemFunctions::Now(LScriptArguments &Args) { Args.GetReturn()->Empty(); Args.GetReturn()->Type = GV_DATETIME; Args.GetReturn()->Value.Date = new LDateTime; Args.GetReturn()->Value.Date->SetNow(); return true; } bool SystemFunctions::New(LScriptArguments &Args) { if (Args.Length() < 1 || !Args[0]) { LAssert(!"Wrong args."); return false; } Args.GetReturn()->Empty(); char *sType = Args[0]->CastString(); if (!sType) return false; if (IsDigit(*sType)) { // Binary block int Bytes = ::atoi(sType); if (!Bytes) return false; return Args.GetReturn()->SetBinary(Bytes, new char[Bytes], true); } LVariant *Ret = Args.GetReturn(); LDomProperty Type = LStringToDomProp(sType); switch (Type) { case TypeList: { Ret->SetList(); break; } case TypeHashTable: { Ret->SetHashTable(); break; } case TypeSurface: { Ret->Empty(); Ret->Type = GV_LSURFACE; if ((Ret->Value.Surface.Ptr = new LMemDC)) { Ret->Value.Surface.Ptr->IncRef(); Ret->Value.Surface.Own = true; } break; } case TypeFile: { Ret->Empty(); #if 1 Ret->Type = GV_STREAM; Ret->Value.Stream.Ptr = new LFile; if (Ret->Value.Stream.Ptr) Ret->Value.Stream.Own = true; #else Ret->Type = GV_GFILE; if ((Ret->Value.File.Ptr = new LFile)) { Ret->Value.File.Ptr->AddRef(); Ret->Value.File.Own = true; } #endif break; } case TypeDateTime: { Ret->Empty(); Ret->Type = GV_DATETIME; Ret->Value.Date = new LDateTime; break; } default: { Ret->Empty(); LCompiledCode *c = Engine ? Engine->GetCurrentCode() : NULL; if (!c) return false; LAutoWString o(Utf8ToWide(sType)); LCustomType *t = c->GetType(o); if (t) { int ArrayLength = Args.Length() > 1 ? Args[1]->CastInt32() : 1; if (ArrayLength > 0) { Ret->Type = GV_CUSTOM; Ret->Value.Custom.Dom = t; Ret->Value.Custom.Data = new uint8_t[t->Sizeof() * ArrayLength]; } } } } return true; } bool SystemFunctions::Len(LScriptArguments &Args) { size_t i = 0; for (LVariant *v: Args) { switch (v->Type) { case GV_LIST: i += v->Value.Lst->Length(); break; case GV_HASHTABLE: i += v->Value.Hash->Length(); break; case GV_BINARY: i += v->Value.Binary.Length; break; case GV_STRING: i += Strlen(v->Value.String); break; case GV_WSTRING: i += Strlen(v->Value.WString); break; default: i += 1; break; } } *Args.GetReturn() = i; return true; } bool SystemFunctions::Delete(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } LVariant *v = Args[0]; if (v->Type == GV_CUSTOM) { DeleteArray(v->Value.Custom.Data); v->Empty(); } else { v->Empty(); } *Args.GetReturn() = true; return true; } class LFileListEntry : public LDom { bool Folder; LVariant Name; int64 Size; LDateTime Modified; public: LFileListEntry(LDirectory *d) { Folder = d->IsDir(); Name = d->GetName(); Size = d->GetSize(); Modified.Set(d->GetLastWriteTime()); } const char *GetClass() override { return "LFileListEntry"; } bool GetVariant(const char *Var, LVariant &Value, const char *Arr = NULL) override { LDomProperty p = LStringToDomProp(Var); switch (p) { case ObjName: Value = Name; break; case ObjLength: Value = Size; break; case FileFolder: Value = Folder; break; case FileModified: Value = &Modified; break; default: return false; } return true; } }; bool SystemFunctions::DeleteFile(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } char *f = Args[0]->CastString(); if (f) *Args.GetReturn() = FileDev->Delete(Args[0]->CastString()); else *Args.GetReturn() = false; return true; } bool SystemFunctions::CurrentScript(LScriptArguments &Args) { LCompiledCode *Code; if (Engine && (Code = Engine->GetCurrentCode())) { *Args.GetReturn() = Code->GetFileName(); return true; } return false; } bool SystemFunctions::PathExists(LScriptArguments &Args) { if (Args.Length() == 0) { LAssert(!"Wrong args."); return false; } LDirectory d; if (d.First(Args[0]->CastString(), NULL)) { if (d.IsDir()) *Args.GetReturn() = 2; else *Args.GetReturn() = 1; } else { *Args.GetReturn() = 0; } return true; } bool SystemFunctions::PathJoin(LScriptArguments &Args) { char p[MAX_PATH_LEN] = ""; for (unsigned i=0; iCastString(); if (i) LMakePath(p, sizeof(p), p, s); else strcpy_s(p, sizeof(p), s); } if (*p) *Args.GetReturn() = p; else Args.GetReturn()->Empty(); return true; } bool SystemFunctions::PathSep(LScriptArguments &Args) { *Args.GetReturn() = DIR_STR; return true; } bool SystemFunctions::ListFiles(LScriptArguments &Args) { if (Args.Length() < 1) { Args.GetReturn()->Empty(); Args.Throw(_FL, "ListFiles(FolderPath[, FilterPattern]) expects at least one argument."); return false; } Args.GetReturn()->SetList(); auto Folder = Args[0]->CastString(); auto Pattern = Args.Length() > 1 ? Args[1]->CastString() : NULL; LDirectory d; for (auto b=d.First(Folder); b; b=d.Next()) { if (!Pattern || MatchStr(Pattern, d.GetName())) Args.GetReturn()->Value.Lst->Insert(new LVariant(new LFileListEntry(&d))); } return true; } bool SystemFunctions::CreateSurface(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { Args.Throw(_FL, "CreateSurface(x, y[, Bits|ColourSpace]) expects at least two arguments."); return false; } auto x = Args[0]->CastInt32(); auto y = Args[1]->CastInt32(); LColourSpace Cs = CsNone; if (Args.Length() > 2) { LVariant *Type = Args[2]; const char *c; if (Type->IsInt()) { // Bit depth... convert to default Colour Space. Cs = LBitsToColourSpace(Type->CastInt32()); } else if ((c = Type->Str())) { // Parse string colour space def Cs = LStringToColourSpace(Type->Str()); } } if (!Cs) // Catch all error cases and make it the default screen depth. Cs = GdcD->GetColourSpace(); auto r = Args.GetReturn(); if ((r->Value.Surface.Ptr = new LMemDC(x, y, Cs))) { r->Type = GV_LSURFACE; r->Value.Surface.Own = true; r->Value.Surface.Ptr->IncRef(); } return true; } bool SystemFunctions::ColourSpaceToString(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() != 1) { Args.Throw(_FL, "ColourSpaceToString(ColourSpaceInt) expects at least one argument."); return false; } *Args.GetReturn() = LColourSpaceToString((LColourSpace)Args[0]->CastInt32()); return true; } bool SystemFunctions::StringToColourSpace(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() != 1) { Args.Throw(_FL, "StringToColourSpace(ColourSpaceStr) expects at least one argument."); return false; } *Args.GetReturn() = (int)LStringToColourSpace(Args[0]->Str()); return true; } bool SystemFunctions::MessageDlg(LScriptArguments &Args) { if (Args.Length() < 2) { Args.Throw(_FL, "MessageDlg(Parent, Message[, Title[, Buttons]]) expects at least 2 arguments."); return false; } auto Parent = CastLView(*Args[0]); auto Msg = Args[1]->Str(); auto Title = Args.IdxCheck(2) ? Args[2]->Str() : LAppInst->Name(); auto Btns = Args.IdxCheck(3) ? Args[3]->CastInt32() : MB_OK; auto Btn = LgiMsg(Parent, Msg, Title, Btns); *Args.GetReturn() = Btn; return true; } bool SystemFunctions::GetInputDlg(LScriptArguments &Args) { if (Args.Length() < 5) { Args.Throw(_FL, "GetInputDlg(Parent, DefaultInput, Message, Title, IsPassword, Callback) expects 5 arguments."); return false; } auto Vm = dynamic_cast(Args.GetVm()); if (!Vm) return false; auto Ctx = Vm->SaveContext(); if (!Ctx) { Args.Throw(_FL, "GetInputDlg requires a valid VM context."); return false; } auto Parent = CastLView(*Args[0]); auto InitVal = Args[1]->Str(); auto Msg = Args[2]->Str(); auto Title = Args[3]->Str(); auto Pass = Args[4]->CastInt32() != 0; auto Callback = Args[5]->Str(); Args.GetReturn()->Empty(); auto Dlg = new LInput(Parent, InitVal, Msg, Title, Pass); Dlg->DoModal([this, Dlg, Ctx, Callback=LString(Callback)](auto d, auto ok) { if (ok) { LScriptArguments Args(NULL); Args.Add(new LVariant(Dlg->GetStr())); Ctx.Call(Callback, Args); Args.DeleteObjects(); } delete Dlg; }); // Don't wait here... return true; } bool SystemFunctions::GetViewById(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } LViewI *Parent = CastLView(*Args[0]); int Id = Args[1]->CastInt32(); if (!Parent || Id <= 0) return false; if (Parent->GetViewById(Id, Args.GetReturn()->Value.View)) { Args.GetReturn()->Type = GV_LVIEW; } return true; } bool SystemFunctions::Execute(LScriptArguments &Args) { if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } LStringPipe p; char *Exe = Args[0]->CastString(); char *Arguments = Args[1]->CastString(); LSubProcess e(Exe, Arguments); bool Status = e.Start(); if (Status) { e.Communicate(&p); LAutoString o(p.NewStr()); *Args.GetReturn() = o; } else if (Log) { uint32_t ErrCode = e.GetErrorCode(); LString ErrMsg = LErrorCodeToString(ErrCode); if (ErrMsg) Log->Print("Error: Execute(\"%s\",\"%s\") failed with '%s'\n", Exe, Arguments, ErrMsg.Get()); else Log->Print("Error: Execute(\"%s\",\"%s\") failed with '0x%x'\n", Exe, Arguments, ErrCode); } return Status; } bool SystemFunctions::System(LScriptArguments &Args) { if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } char *Exe = Args[0]->Str(); char *Arg = Args[1]->Str(); *Args.GetReturn() = LExecute(Exe, Arg); return true; } bool SystemFunctions::OsName(LScriptArguments &Args) { *Args.GetReturn() = LGetOsName(); return true; } bool SystemFunctions::OsVersion(LScriptArguments &Args) { LArray Ver; LGetOs(&Ver); Args.GetReturn()->SetList(); for (int i=0; i<3; i++) Args.GetReturn()->Value.Lst->Insert(new LVariant(Ver[i])); return true; } #define DefFn(Name) \ LHostFunc(#Name, 0, (ScriptCmd)&SystemFunctions::Name) LHostFunc SystemLibrary[] = { // Debug DefFn(Assert), DefFn(Throw), DefFn(DebuggerEnabled), // String handling DefFn(LoadString), DefFn(FormatSize), DefFn(Sprintf), DefFn(Print), DefFn(ToString), DefFn(Lgi4CC), // Containers/objects DefFn(New), DefFn(Delete), DefFn(Len), // Files DefFn(ReadTextFile), DefFn(WriteTextFile), DefFn(SelectFiles), DefFn(SelectFolder), DefFn(ListFiles), DefFn(DeleteFile), DefFn(CurrentScript), DefFn(PathJoin), DefFn(PathExists), DefFn(PathSep), // Time DefFn(ClockTick), DefFn(Sleep), DefFn(Now), // Images DefFn(CreateSurface), DefFn(ColourSpaceToString), DefFn(StringToColourSpace), // UI DefFn(MessageDlg), DefFn(GetInputDlg), DefFn(GetViewById), // System DefFn(Execute), DefFn(System), DefFn(OsName), DefFn(OsVersion), // End of list marker LHostFunc(0, 0, 0), }; LHostFunc *SystemFunctions::GetCommands() { return SystemLibrary; } diff --git a/src/common/Coding/ScriptVM.cpp b/src/common/Coding/ScriptVM.cpp --- a/src/common/Coding/ScriptVM.cpp +++ b/src/common/Coding/ScriptVM.cpp @@ -1,2160 +1,2167 @@ #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; } + static bool InException = false; + if (InException) + return; + + InException = true; 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, Asm)) { LAssert(Debugger->GetCode()); // It should have taken a copy of the Code we passed in 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; } + + InException = false; } 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) { 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(); #define ON_EXCEPTION(args) \ args.Length(0); \ args.Address = -1; \ if (args.HasException()) \ { \ Status = ScriptError; \ goto ExitExecutionLoop; \ } if (Type == RunContinue && BreakPts.Length() == 0) { // Unconstrained execution while (c.u8 < e) { #if TIME_INSTRUCTIONS uint8 TimedOpCode = *c.u8; QueryPerformanceCounter(&start); #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."); } } ExitExecutionLoop: 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) { 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 LAutoPtr Vm; LAutoPtr Obj; LVmCallback *Callback = NULL; LString Script, Assembly; LArray Blocks; ssize_t CurrentAddr = -1; LArray LineIsAsm; LVariant Return; bool AcceptNotify = false; // Ui 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(L_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=%i b.AsmAddr[%i,%i]=%u\n", (int)d->CurrentAddr, i, n, b.AsmAddr[n]); if (d->CurrentAddr >= b.AsmAddr[n]) { LgiTrace("b.ViewLine=%i b.SrcLines=%i n=%i\n", b.ViewLine, b.SrcLines, 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, LAutoPtr Vm, LAutoPtr Code, const char *Assembly) { d = new LScriptVmDebuggerPriv; d->Parent = Parent; d->Vm = Vm; d->Callback = Callback; 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()) { auto 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 (d->Obj && d->Obj->GetFileName()) { if (_stricmp(p, d->Obj->GetFileName()) == 0) Match = it; } d->SourceLst->Insert(it); } } } if (!Match && d->Obj) { LListItem *it = new LListItem; if (it) { 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() { } bool LVmDebuggerWnd::OnRequestClose(bool OsShuttingDown) { return LWindow::OnRequestClose(OsShuttingDown); } void LVmDebuggerWnd::Run() { Visible(true); } LStream *LVmDebuggerWnd::GetLog() { return d->Log; } 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: { Quit(); 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/Coding/ScriptingPriv.h b/src/common/Coding/ScriptingPriv.h --- a/src/common/Coding/ScriptingPriv.h +++ b/src/common/Coding/ScriptingPriv.h @@ -1,597 +1,630 @@ #ifndef _GSCRIPTING_PRIV_H_ #define _GSCRIPTING_PRIV_H_ #include #include "lgi/common/Scripting.h" #include "lgi/common/RefCount.h" // Instructions #define _i(name, opcode, desc) \ name = opcode, #define AllInstructions \ _i(INop, 0, "Nop") \ - _i(IAssign, OpAssign, "OpAssign") \ - _i(IPlus, OpPlus, "OpPlus") \ - _i(IUnaryPlus, OpUnaryPlus, "OpUnaryPlus") \ - _i(IMinus, OpMinus, "OpMinus") \ - _i(IUnaryMinus, OpUnaryMinus, "OpUnaryMinus") \ - _i(IMul, OpMul, "OpMul") \ - _i(IDiv, OpDiv, "OpDiv") \ - _i(IMod, OpMod, "OpMod") \ - _i(ILessThan, OpLessThan, "OpLessThan") \ - _i(ILessThanEqual, OpLessThanEqual, "OpLessThanEqual") \ - _i(IGreaterThan, OpGreaterThan, "OpGreaterThan") \ - _i(IGreaterThanEqual, OpGreaterThanEqual, "OpGreaterThanEqual") \ - _i(IEquals, OpEquals, "OpEquals") \ - _i(INotEquals, OpNotEquals, "OpNotEquals") \ - _i(IPlusEquals, OpPlusEquals, "OpPlusEquals") \ - _i(IMinusEquals, OpMinusEquals, "OpMinusEquals") \ - _i(IMulEquals, OpMulEquals, "OpMulEquals") \ - _i(IDivEquals, OpDivEquals, "OpDivEquals") \ - _i(IPostInc, OpPostInc, "OpPostInc") \ - _i(IPostDec, OpPostDec, "OpPostDec") \ - _i(IPreInc, OpPreInc, "OpPreInc") \ - _i(IPreDec, OpPreDec, "OpPreDec") \ - _i(IAnd, OpAnd, "OpAnd") \ - _i(IOr, OpOr, "OpOr") \ - _i(INot, OpNot, "OpNot") \ + _i(IAssign, OpAssign, "IAssign") \ + _i(IPlus, OpPlus, "IPlus") \ + _i(IUnaryPlus, OpUnaryPlus, "IUnaryPlus") \ + _i(IMinus, OpMinus, "IMinus") \ + _i(IUnaryMinus, OpUnaryMinus, "IUnaryMinus") \ + _i(IMul, OpMul, "IMul") \ + _i(IDiv, OpDiv, "IDiv") \ + _i(IMod, OpMod, "IMod") \ + _i(ILessThan, OpLessThan, "ILessThan") \ + _i(ILessThanEqual, OpLessThanEqual, "ILessThanEqual") \ + _i(IGreaterThan, OpGreaterThan, "IGreaterThan") \ + _i(IGreaterThanEqual, OpGreaterThanEqual, "IGreaterThanEqual") \ + _i(IEquals, OpEquals, "IEquals") \ + _i(INotEquals, OpNotEquals, "INotEquals") \ + _i(IPlusEquals, OpPlusEquals, "IPlusEquals") \ + _i(IMinusEquals, OpMinusEquals, "IMinusEquals") \ + _i(IMulEquals, OpMulEquals, "IMulEquals") \ + _i(IDivEquals, OpDivEquals, "IDivEquals") \ + _i(IPostInc, OpPostInc, "IPostInc") \ + _i(IPostDec, OpPostDec, "IPostDec") \ + _i(IPreInc, OpPreInc, "IPreInc") \ + _i(IPreDec, OpPreDec, "IPreDec") \ + _i(IAnd, OpAnd, "IAnd") \ + _i(IOr, OpOr, "IOr") \ + _i(INot, OpNot, "INot") \ + _i(IBitwiseAnd, OpBitwiseAnd, "IBitwiseAnd") \ + _i(IBitwiseOr, OpBitwiseOr, "IBitwiseOr") \ + _i(IBitwiseNegate, OpBitwiseNegate, "IBitwiseNegate") \ + _i(IBitwiseXor, OpBitwiseXor, "IBitwiseXor") \ + _i(IShiftLeft, OpShiftLeft, "IShiftLeft") \ + _i(IShiftRight, OpShiftRight, "IShiftRight") \ \ /** Calls a another part of the script */ \ _i(ICallScript, 64, "CallScript") \ /** Calls a method defined by the script context */ \ _i(ICallMethod, 65, "CallMethod") \ _i(IDomGet, 67, "DomGet") \ _i(IDomSet, 68, "DomSet") \ _i(IPush, 69, "Push") \ _i(IPop, 70, "Pop") \ _i(IJump, 71, "Jump") \ _i(IJumpZero, 72, "JumpZ") \ _i(IArrayGet, 73, "ArrayGet") \ _i(IArraySet, 74, "ArraySet") \ _i(IRet, 75, "Return") \ _i(IDomCall, 76, "DomCall") \ /* Stop in the VM at instruction */ \ _i(IBreakPoint, 77, "BreakPoint") \ _i(ICast, 78, "Cast") \ /* Open the debugger */ \ _i(IDebug, 79, "Debug") \ -enum GInstruction { +enum LInstruction { AllInstructions }; enum OperatorType { OpPrefix, OpInfix, OpPostfix, }; extern char16 sChar[]; extern char16 sInt[]; extern char16 sUInt[]; extern char16 sInt32[]; extern char16 sUInt32[]; extern char16 sInt64[]; extern char16 sHWND[]; extern char16 sDWORD[]; extern char16 sLPTSTR[]; extern char16 sLPCTSTR[]; extern char16 sElse[]; extern char16 sIf[]; extern char16 sFunction[]; extern char16 sExtern[]; extern char16 sFor[]; extern char16 sWhile[]; extern char16 sReturn[]; extern char16 sInclude[]; extern char16 sDefine[]; extern char16 sStruct[]; extern char16 sTrue[]; extern char16 sFalse[]; extern char16 sNull[]; extern char16 sOutParam[]; extern char16 sHash[]; extern char16 sPeriod[]; extern char16 sComma[]; extern char16 sSemiColon[]; extern char16 sStartRdBracket[]; extern char16 sEndRdBracket[]; extern char16 sStartSqBracket[]; extern char16 sEndSqBracket[]; extern char16 sStartCurlyBracket[]; extern char16 sEndCurlyBracket[]; -extern const char *InstToString(GInstruction i); +extern const char *InstToString(LInstruction i); /* Variable Reference: Can either be a: - stack variable - global variable - register (0 .. MAX_REGISTER - 1) Thus a variable reference encodes 2 pieces of information, first the scope of the reference (as above) and secondly the index into that scope. Scopes are stored as arrays of variables. The scope is one byte, the index is 3 bytes following, totally 4 bytes per ref. */ #define MAX_REGISTER 16 /// 32bit variable reference, used to track where a variable is during compilation. struct LVarRef { /// \sa #SCOPE_REGISTER, #SCOPE_LOCAL or #SCOPE_GLOBAL unsigned Scope : 8; /// Index into scope int Index : 24; bool Valid() { return Index >= 0; } void Empty() { Scope = 0; Index = -1; } bool IsReg() { return Scope == SCOPE_REGISTER && Index >= 0 && Index < MAX_REGISTER; } void SetReg(int i) { Scope = SCOPE_REGISTER; Index = i; } bool operator ==(LVarRef &r) { return r.Scope == Scope && r.Index == Index; } bool operator !=(LVarRef &r) { return r.Scope != Scope || r.Index != Index; } const char *GetStr() { if (Index < 0) { LAssert(!"Invalid reference"); return "NoRef"; } #define GETSTR_BUF_SIZE 16 static char Buf[8][GETSTR_BUF_SIZE]; static int Cur = 0; static char Names[] = {'R', 'L', 'G'}; char *b = Buf[Cur++]; if (Cur >= 8) Cur = 0; LAssert(Scope <= SCOPE_GLOBAL); sprintf_s(b, GETSTR_BUF_SIZE, "%c%i", Names[Scope], Index); return b; } }; union LScriptPtr { uint8_t *u8; uint16 *u16; uint32_t *u32; int8 *i8; int16 *i16; int32 *i32; double *dbl; float *flt; LVarRef *r; LFunc **fn; }; class SystemFunctions; class LCompileTools { protected: OperatorType OpType(LOperator o) { switch (o) { case OpUnaryPlus: case OpUnaryMinus: case OpPreInc: case OpPreDec: case OpNot: + case OpBitwiseNegate: return OpPrefix; case OpPostInc: case OpPostDec: return OpPostfix; default: return OpInfix; } } int GetPrecedence(LOperator o) { // Taken from: // http://www.cppreference.com/operator_precedence.html switch (o) { case OpAssign: case OpMinusEquals: case OpPlusEquals: case OpMulEquals: case OpDivEquals: return 16; + case OpOr: + return 15; + case OpAnd: + return 14; + + case OpBitwiseOr: return 13; - case OpOr: - return 14; + case OpBitwiseXor: + return 12; + + case OpBitwiseAnd: + return 11; case OpEquals: case OpNotEquals: - return 9; + return 10; case OpLessThan: case OpLessThanEqual: case OpGreaterThan: case OpGreaterThanEqual: - return 8; + return 9; + + case OpShiftLeft: + case OpShiftRight: + return 7; case OpPlus: case OpMinus: return 6; case OpMul: case OpDiv: case OpMod: return 5; case OpUnaryPlus: case OpUnaryMinus: case OpPreInc: case OpPreDec: case OpNot: + case OpBitwiseNegate: return 3; case OpPostInc: case OpPostDec: return 2; case OpNull: return 0; default: LAssert(!"Really?"); break; } return -1; } LOperator IsOp(char16 *s, int PrevIsOp) { if (!s) return OpNull; if (s[0] != 0 && !s[1]) { // One character operator switch (*s) { case '=': return OpAssign; case '*': return OpMul; case '/': return OpDiv; case '<': return OpLessThan; case '>': return OpGreaterThan; case '%': return OpMod; case '!': return OpNot; case '+': { if (PrevIsOp == 0) return OpPlus; return OpUnaryPlus; } case '-': { if (PrevIsOp == 0) return OpMinus; return OpUnaryMinus; } + case '&': return OpBitwiseAnd; + case '|': return OpBitwiseOr; + case '~': return OpBitwiseNegate; + case '^': return OpBitwiseXor; } } else if (s[0] != 0 && s[1] == '=' && !s[2]) { // 2 chars, "something" equals operator switch (*s) { case '!': return OpNotEquals; case '=': return OpEquals; case '<': return OpLessThanEqual; case '>': return OpGreaterThanEqual; case '+': return OpPlusEquals; case '-': return OpMinusEquals; case '*': return OpMulEquals; case '/': return OpDivEquals; } } else if (s[0] == '+' && s[1] == '+' && !s[2]) { if (PrevIsOp == 0) return OpPostInc; return OpPreInc; } else if (s[0] == '-' && s[1] == '-' && !s[2]) { if (PrevIsOp == 0) return OpPostDec; return OpPreDec; } else if (s[0] == '&' && s[1] == '&' && !s[2]) { return OpAnd; } else if (s[0] == '|' && s[1] == '|' && !s[2]) { return OpOr; } + else if (s[0] == '<' && s[1] == '<' && !s[2]) + { + return OpShiftLeft; + } + else if (s[0] == '>' && s[1] == '>' && !s[2]) + { + return OpShiftRight; + } return OpNull; } }; /// This class compiles the source down to byte code class LCompiler : public LScriptUtils { class LCompilerPriv *d; public: /// Constructor LCompiler(); ~LCompiler(); /// Compile the source into byte code. bool Compile ( LAutoPtr &Code, LScriptContext *SysContext, LScriptContext *UserContext, const char *FileName, const char *Script, LDom *Args ); }; /// This class is the VM for the byte language class LVirtualMachine : public LVirtualMachineI, public LScriptUtils { friend class LVmDebuggerWnd; friend class LScriptArguments; class LVirtualMachinePriv *d; public: static bool BreakOnWarning; class Context { friend class LVirtualMachine; LCompiledCode *Code = NULL; LVmCallback *Callback = NULL; ssize_t Addr = -1; public: operator bool() { return Code != NULL && Callback != NULL; } bool Call(LString CallbackName, LScriptArguments &Args) const { if (!Callback) return false; LVirtualMachine Vm(*this); auto Prev = Args.GetVm(); Args.SetVm(&Vm); auto result = Callback->CallCallback(Vm, CallbackName, Args); Args.SetVm(Prev); return result; } }; LVirtualMachine(LVmCallback *callback = NULL); LVirtualMachine(Context ctx); LVirtualMachine(LVirtualMachine *vm); ~LVirtualMachine(); void OnException(const char *File, int Line, ssize_t Address, const char *Msg); /// Executes the whole script starting at the top LExecutionStatus Execute ( /// [In] The code to execute LCompiledCode *Code, /// [In] The instruction to start at... [defaults to the start of script) uint32_t StartOffset = 0, /// [Optional] Log file for execution LStream *Log = NULL, /// Start the script execution straight away? bool StartImmediately = true, /// Optional return value LVariant *Return = NULL ); /// Execute just one method and return LExecutionStatus ExecuteFunction ( /// [In] The code to execute LCompiledCode *Code, /// [In] The function to execute LFunctionInfo *Func, /// [In/Out] The function's arguments LScriptArguments &Args, /// [Optional] Log file for execution LStream *Log = NULL, /// [Optional] Copy arguments back to this array LScriptArguments *ArgsOut = NULL ); // Debugging commands LVmDebugger *OpenDebugger(LCompiledCode *Code = NULL, const char *Assembly = NULL); bool StepInstruction(); bool StepLine(); bool StepOut(); bool BreakExecution(); bool Continue(); bool BreakPoint(const char *File, int Line, bool Add); bool BreakPoint(int Addr, bool Add); void SetBreakCpp(bool Brk); void SetDebuggerEnabled(bool b); // Other methods void SetTempPath(const char *Path); LVmCallback *GetCallback(); Context SaveContext(); }; /// Scripting engine system functions class SystemFunctions : public LScriptContext { LScriptEngine *Engine; LStream *Log; #ifdef WINNATIVE HANDLE Brk; #endif LView *CastLView(LVariant &v); public: SystemFunctions(); ~SystemFunctions(); LStream *GetLog() override; bool SetLog(LStream *log) override; void SetEngine(LScriptEngine *Eng); LString GetIncludeFile(const char *FileName) override { return LString(); } LHostFunc *GetCommands() override; // Debug bool Assert(LScriptArguments &Args); bool Throw(LScriptArguments &Args); bool DebuggerEnabled(LScriptArguments &Args); // String bool LoadString(LScriptArguments &Args); /// Formats a string bool Sprintf(LScriptArguments &Args); /// Formats a file size bool FormatSize(LScriptArguments &Args); /// Prints items to the console bool Print(LScriptArguments &Args); /// Converts args to string bool ToString(LScriptArguments &Args); /// Turn a 4 char string into an int bool Lgi4CC(LScriptArguments &Args); // Object creation/deletion bool New(LScriptArguments &Args); bool Delete(LScriptArguments &Args); bool Len(LScriptArguments &Args); // File /// Reads a text file into a variable bool ReadTextFile(LScriptArguments &Args); /// Writes a text file from a variable bool WriteTextFile(LScriptArguments &Args); /// \brief Opens a file open dialog to select files. /// /// Args: LView *Parent, char *Patterns, /// char *InitFolder, bool Multiselect bool SelectFiles(LScriptArguments &Args); /// Open a folder select dialog /// /// Args: LView *Parent, char *InitFolder bool SelectFolder(LScriptArguments &Args); /// Lists file in folder /// /// Args; char *Path, [optional] char *Pattern /// Returns: List of DOM objects with the following fields: /// Name - File/dir name /// Size - Size of entry /// Folder - bool, true if folder /// Modified - LDateTime, modified time bool ListFiles(LScriptArguments &Args); /// Deletes a file bool DeleteFile(LScriptArguments &Args); /// Gets the current script path. bool CurrentScript(LScriptArguments &Args); /// Finds out if a path exists. bool PathExists(LScriptArguments &Args); /// Joins path segments together. bool PathJoin(LScriptArguments &Args); /// Returns the current OS path separator. bool PathSep(LScriptArguments &Args); // Time /// Sleeps a number of milliseconds bool Sleep(LScriptArguments &Args); /// Get the current tick count bool ClockTick(LScriptArguments &Args); /// Get the date time bool Now(LScriptArguments &Args); // Bitmaps /// Creates a memory context bool CreateSurface(LScriptArguments &Args); bool ColourSpaceToString(LScriptArguments &Args); bool StringToColourSpace(LScriptArguments &Args); // User interface /// Standard alert message box bool MessageDlg(LScriptArguments &Args); /// Gets an input string from the user /// String GetInputDlg(Window Parent, String InitialValue, String Question, String Title, bool IsPassword, String Callback. bool GetInputDlg(LScriptArguments &Args); /// Gets a view by id bool GetViewById(LScriptArguments &Args); // System /// Executes a command, waits for it to finish, then returns it's output: /// String Execute(String Application, String CmdLine); bool Execute(LScriptArguments &Args); /// Executes a command and doesn't wait for it to return: /// Bool System(String Application, String CmdLine); bool System(LScriptArguments &Args); /// Gets the operating system name. bool OsName(LScriptArguments &Args); /// Gets the operating system version. bool OsVersion(LScriptArguments &Args); }; #endif diff --git a/test/ScriptingUnitTests/LgiScript.vcxproj b/test/ScriptingUnitTests/LgiScript.vcxproj --- a/test/ScriptingUnitTests/LgiScript.vcxproj +++ b/test/ScriptingUnitTests/LgiScript.vcxproj @@ -1,225 +1,224 @@  Debug Win32 Debug x64 Release Win32 Release x64 LgiScript {D21A823F-9A21-43BB-8F2C-FA665D16126B} LgiScript Win32Proj 10.0 Application v142 Unicode true Application v142 Unicode Application v142 Unicode true Application v142 Unicode <_ProjectFileVersion>12.0.30501.0 $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ true $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ true $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ false $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ false Disabled ..\..\include;..\..\include\lgi\win;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_CONSOLE;WINDOWS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level2 ProgramDatabase imm32.lib;%(AdditionalDependencies) true Console MachineX86 X64 Disabled ..\..\include;..\..\include\lgi\win;%(AdditionalIncludeDirectories) WIN64;_DEBUG;_CONSOLE;WINDOWS;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL Level2 ProgramDatabase imm32.lib;%(AdditionalDependencies) true Console MachineX64 MaxSpeed true ..\..\include;..\..\include\lgi\win;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_CONSOLE;WINDOWS;%(PreprocessorDefinitions) MultiThreadedDLL true Level2 ProgramDatabase imm32.lib;%(AdditionalDependencies) true Console true true MachineX86 X64 MaxSpeed true ..\..\include;..\..\include\lgi\win;%(AdditionalIncludeDirectories) WIN64;NDEBUG;_CONSOLE;WINDOWS;%(PreprocessorDefinitions) MultiThreadedDLL true Level2 ProgramDatabase imm32.lib;%(AdditionalDependencies) true Console true true MachineX64 - - - - cd $(IntDir) "c:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64\ml64.exe" %(FullPath) /c $(IntDir)%(Filename).obj;%(Outputs) cd $(IntDir) "c:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64\ml64.exe" %(FullPath) /c $(IntDir)%(Filename).obj;%(Outputs) + + + + + - - {95df9ca4-6d37-4a85-a648-80c2712e0da1} false \ No newline at end of file diff --git a/test/ScriptingUnitTests/LgiScript.vcxproj.filters b/test/ScriptingUnitTests/LgiScript.vcxproj.filters --- a/test/ScriptingUnitTests/LgiScript.vcxproj.filters +++ b/test/ScriptingUnitTests/LgiScript.vcxproj.filters @@ -1,91 +1,104 @@  {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {2560d89b-1bfb-4676-b2de-a8afd5231429} {2e2d13df-4e87-4b6b-82ae-f301b34bdd22} {43a1e628-add4-441b-8ca4-ffea4bf40ba5} {9d89d250-b2ed-4a08-8963-629ca0d826cf} - - Source Files + + Source Files\Scripting - - Source Files + + Source Files\Scripting - Source Files + Source Files\Scripting - Source Files + Source Files\Scripting - Source Files + Source Files\Scripting - + Source Files Source Files\Test Scripts Source Files\Test Scripts Source Files\Test Scripts Source Files\Test Scripts Source Files\Test Scripts Source Files\Test Scripts Source Files\Test Scripts Source Files\Test Scripts Source Files\Test Scripts Source Files\Test Scripts Source Files\Test Scripts - - - - + + Documentation + + + Documentation + + + Documentation + + + Documentation + + + Source Files\Test Scripts + - - - - + + Source Files\Scripting\Hdrs + + + Source Files\Scripting\Hdrs + - Source Files + Source Files\Scripting \ No newline at end of file diff --git a/test/ScriptingUnitTests/Main.cpp b/test/ScriptingUnitTests/Main.cpp --- a/test/ScriptingUnitTests/Main.cpp +++ b/test/ScriptingUnitTests/Main.cpp @@ -1,164 +1,167 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "../src/common/Coding/ScriptingPriv.h" #include "lgi/common/StringClass.h" #include "lgi/common/LgiRes.h" struct ConsoleLog : public LStream { ssize_t Write(const void *Ptr, ssize_t Size, int Flags) { return printf("%.*s", (int)Size, (char*)Ptr); } }; -class App : public LApp, public LScriptContext, public LVmCallback +class App : + public LApp, + public LScriptContext, + public LVmCallback { LScriptEngine *Engine; - LAutoString SrcFile; + LString SrcFile; ConsoleLog Log; + LStream *GetLog() + { + return &Log; + } + bool CallCallback(LVirtualMachine &Vm, LString CallbackName, LScriptArguments &Args) { Args.Throw(_FL, "Not implemented."); return false; } LVmDebugger *AttachVm(LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) { LAutoPtr vm(new LVirtualMachine(Vm)); LAutoPtr code(new LCompiledCode(*Code)); return new LVmDebuggerWnd(NULL, this, vm, code, Assembly); } bool CompileScript(LAutoPtr &Output, const char *FileName, const char *Source) { return false; } public: int Status; const char* GetClass() override { return "App"; } App(OsAppArguments &AppArgs) : LApp(AppArgs, "LgiScript") { LFile::Path p(LSP_APP_INSTALL); p += "Resources"; p += "LgiScript.lr8"; LgiGetResObj(true, p); Engine = NULL; Status = 0; } LHostFunc *GetCommands() { return NULL; } void SetEngine(LScriptEngine *Eng) { Engine = Eng; } void OnReceiveFiles(LArray &Files) { - for (int i=0; iGetOption("disassemble"); + auto Disassemble = LAppInst->GetOption("disassemble"); if (!LFileExists(File)) { printf("Error: '%s' not found.\n", File); return false; } - if (!SrcFile.Reset(NewStr(File))) - { - printf("Error: Mem alloc failed.\n"); - return false; - } - - LScriptEngine Eng(NULL, NULL, this); + SrcFile = File; + LScriptEngine Eng(NULL, this, this); Eng.SetConsole(&Log); auto Src = LReadFile(SrcFile); if (!Src) { printf("Error: Failed to read '%s'.\n", SrcFile.Get()); return false; } LAutoPtr Obj; if (!Eng.Compile(Obj, NULL, Src, File)) { printf("Error: Compilation failed '%s'.\n", SrcFile.Get()); return false; } LVariant Ret; - LExecutionStatus s = Eng.Run(Obj, &Ret); + auto s = Eng.Run(Obj, &Ret); if (s == ScriptError) { printf("Error: Execution failed '%s'.\n", SrcFile.Get()); return false; } else if (s == ScriptWarning) { printf("Warning: Execution succeeded with warnings '%s'.\n", SrcFile.Get()); return false; } if (Ret.CastInt32()) printf("Success: %s\n", File); else { printf("Failed: %s\n", File); s = ScriptError; } if (Disassemble) { LString f = File; int Idx = f.RFind("."); if (Idx > 0) { f = f(0, Idx) + ".asm"; auto a = LReadFile(f); if (a) { printf("%s\n", a.Get()); } } } return s == ScriptSuccess; } }; int LgiMain(OsAppArguments &AppArgs) { App a(AppArgs); if (a.IsOk()) { a.OnCommandLine(); } return a.Status; } \ No newline at end of file diff --git a/test/ScriptingUnitTests/Scripts/BitwiseOps.script b/test/ScriptingUnitTests/Scripts/BitwiseOps.script new file mode 100644 --- /dev/null +++ b/test/ScriptingUnitTests/Scripts/BitwiseOps.script @@ -0,0 +1,46 @@ + +a = 0x04; +b = a | 0x80; +if (b != 0x84) +{ + Print("Bitwise OR failed.\n"); + return false; +} + +b = a & 0x84; +if (b != 0x04) +{ + Print("Bitwise AND failed.\n"); + return false; +} + +b = ~0xaa; +if (b != -171) +{ + Print("Bitwise NEGATE failed.\n"); + return false; +} + +b = a ^ 0x84; +if (b != 0x80) +{ + Print("Bitwise XOR failed.\n"); + return false; +} + +b = a << 4; +if (b != 0x40) +{ + Print("<< operator failed.\n"); + return false; +} + +b = a >> 1; +if (b != 0x2) +{ + Print(">> operator failed.\n"); + return false; +} + +return true; + diff --git a/test/UnitTests/win/UnitTests.vcxproj b/test/UnitTests/win/UnitTests.vcxproj --- a/test/UnitTests/win/UnitTests.vcxproj +++ b/test/UnitTests/win/UnitTests.vcxproj @@ -1,280 +1,277 @@  Debug Win32 Debug x64 Release Win32 Release x64 {18BF30E1-D77B-496E-8761-99A426DD3B41} 10.0 Application v142 false MultiByte Application v142 false MultiByte Application v142 false MultiByte Application v142 false MultiByte <_ProjectFileVersion>12.0.30501.0 $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ true true $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ false false $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ .\Debug/UnitTests.tlb Disabled ..\..\..\include;..\..\..\include\lgi\win;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_CONSOLE;WINDOWS;LGI_UNIT_TESTS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL true $(IntDir)/UnitTests.pch $(IntDir) $(IntDir) $(IntDir)vc$(PlatformToolsetVersion).pdb true ProgramDatabase _DEBUG;%(PreprocessorDefinitions) 0x0c09 $(OutDir)$(TargetName)$(TargetExt) true true $(IntDir)/UnitTests.pdb Console false MachineX86 true .\Debug/UnitTests.bsc .\Debug/UnitTests.tlb Disabled ..\..\..\include;..\..\..\include\lgi\win;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_CONSOLE;WINDOWS;LGI_UNIT_TESTS;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL true $(IntDir)/UnitTests.pch $(IntDir) $(IntDir) $(IntDir)vc$(PlatformToolsetVersion).pdb true ProgramDatabase Level3 _DEBUG;%(PreprocessorDefinitions) 0x0c09 true true $(IntDir)/UnitTests.pdb Console false true .\Debug/UnitTests.bsc .\Release/UnitTests.tlb MinSpace OnlyExplicitInline ..\..\include;..\..\include\lgi\win;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_CONSOLE;WINDOWS;LGI_UNIT_TESTS;%(PreprocessorDefinitions) true MultiThreadedDLL true true $(IntDir)/UnitTests.pch $(IntDir) $(IntDir) $(IntDir)vc$(PlatformToolsetVersion).pdb true NDEBUG;%(PreprocessorDefinitions) 0x0c09 $(OutDir)$(TargetName)$(TargetExt) true $(IntDir)/UnitTests.pdb Console false MachineX86 true .\Release/UnitTests.bsc .\Release/UnitTests.tlb MinSpace OnlyExplicitInline ..\..\include;..\..\include\lgi\win;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_CONSOLE;WINDOWS;LGI_UNIT_TESTS;%(PreprocessorDefinitions) true MultiThreadedDLL true true $(IntDir)/UnitTests.pch $(IntDir) $(IntDir) $(IntDir)vc$(PlatformToolsetVersion).pdb true NDEBUG;%(PreprocessorDefinitions) 0x0c09 $(OutDir)$(TargetName)$(TargetExt) true $(IntDir)/UnitTests.pdb Console false true .\Release/UnitTests.bsc - - - {95df9ca4-6d37-4a85-a648-80c2712e0da1} \ No newline at end of file diff --git a/test/UnitTests/win/UnitTests.vcxproj.filters b/test/UnitTests/win/UnitTests.vcxproj.filters --- a/test/UnitTests/win/UnitTests.vcxproj.filters +++ b/test/UnitTests/win/UnitTests.vcxproj.filters @@ -1,83 +1,74 @@  {1b5d1f69-ef66-4844-baa5-25ec23d2151c} cpp;c;cxx;rc;def;r;odl;idl;hpj;bat {a734023f-ceb2-4882-9efc-5719c6bf4c8d} h;hpp;hxx;hm;inl {134d3911-e459-4919-8af8-82198f95a179} {8b34f4e6-17d2-42af-8c12-1386ba1acae0} - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - + Source Files - - Source Files + + Source Files\Testing + + + Source Files\Testing + + + Source Files\Testing - - Lgi + + Source Files\Testing - + + Source Files\Testing + + + Source Files\Testing + + Source Files\Testing - - Source Files + + Source Files\Testing + + + Source Files\Testing - - Source Files + + Source Files\Testing - + + Source Files\Testing + + + Source Files\Testing + + Lgi - - Source Files - - - Source Files + + Lgi - - Header Files - - + Header Files - - Header Files - - - Source Files\Testing - - + Lgi \ No newline at end of file