diff --git a/Ide/LgiIdeProj.xml b/Ide/LgiIdeProj.xml --- a/Ide/LgiIdeProj.xml +++ b/Ide/LgiIdeProj.xml @@ -1,280 +1,279 @@ - + - + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - + + + + + + - + win\Makefile.windows ./linux/Makefile.linux ./MacCocoa/LgiIde.xcodeproj Makefile.haiku ./lgiide ./lgiide LgiIde.exe LgiIde.exe WINDOWS WINNATIVE WINDOWS WINNATIVE POSIX POSIX LIBPNG_VERSION=\"1.2\" LIBPNG_VERSION=\"1.2\" MingW /home/matthew/Code/Lgi/trunk/Ide/Code/IdeProjectSettings.cpp ./src ./resources ../include ./src ./resources ../include ..\include\lgi\win ..\include\lgi\win ../include/lgi/linux ../include/lgi/linux/Gtk ../../../../codelib/openssl/include ../include/lgi/linux ../include/lgi/linux/Gtk ../../../../codelib/openssl/include ../include/lgi/haiku ../include/lgi/haiku ../include/lgi/mac/cocoa ../include/lgi/mac/cocoa imm32 imm32 magic pthread `pkg-config --libs gtk+-3.0` -static-libgcc magic pthread `pkg-config --libs gtk+-3.0` -static-libgcc -static-libgcc gnu network be -static-libgcc gnu network be - + Executable lgiide lgiide LgiIde.exe LgiIde.exe lgiide lgiide `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gtk+-3.0` - POSIX _GNU_SOURCE POSIX _GNU_SOURCE - + 4 4 0 1 SOME_TEST=testing - /home/matthew/code/i.Hex/trunk/iHexProject.xml + /home/matthew/code/lgi/trunk/Lvc/LvcProject.xml - + diff --git a/Ide/src/SimpleCppParser.cpp b/Ide/src/SimpleCppParser.cpp --- a/Ide/src/SimpleCppParser.cpp +++ b/Ide/src/SimpleCppParser.cpp @@ -1,1007 +1,1010 @@ /* Known bugs: 1) Duplicate brackets in #defines, e.g: #if SOME_DEF if (expr) { #else if (different_expr) { #endif Breaks the bracket counting. Causing the depth to get out of sync with capture... Workaround: Move the brackets outside of the #define'd area if possible. 2) This syntax is wrongly assumed to be a structure: struct netconn* netconn_alloc(enum netconn_type t, netconn_callback callback) { ... } Can't find these symbols: do_recv (in api_msg.c) process_apcp_msg (in ape-apcp.c) recv_udp (in api_msg.c) Wrong position: lwip_select (out by 4 lines?) nad_apcp_send tcpip_callback_with_block appears 3 times? */ #include "lgi/common/Lgi.h" #include "lgi/common/DocView.h" #include "ParserCommon.h" -// #define DEBUG_FILE "Scribe.h" -// #define DEBUG_LINE 336 +// #define DEBUG_FILE "Lvc.h" +// #define DEBUG_LINE 150 const char *TypeToStr(DefnType t) { switch (t) { default: case DefnNone: return "DefnNone"; case DefnDefine: return "DefnDefine"; case DefnFunc: return "DefnFunc"; case DefnClass: return "DefnClass"; case DefnEnum: return "DefnEnum"; case DefnEnumValue: return "DefnEnumValue"; case DefnTypedef: return "DefnTypedef"; case DefnVariable: return "DefnVariable"; } } bool IsFirst(LArray &a, int depth) { if (depth == 0) return true; for (int i=0; i 1) return false; return true; } bool IsFuncNameChar(char c) { return strchr("_:=~[]<>-+", c) || IsAlpha(c) || IsDigit(c); } bool ParseFunction(LRange &Return, LRange &Name, LRange &Args, const char *Defn) { if (!Defn) return false; int Depth = 0; const char *c, *Last = NULL; for (c = Defn; *c; c++) { if (*c == '(') { if (Depth == 0) Last = c; Depth++; } else if (*c == ')') { if (Depth > 0) Depth--; else { LgiTrace("%s:%i - Fn parse error '%s' (%i)\n", _FL, Defn, (int)(c - Defn)); return false; } } } if (!Last) return false; Args.Start = Last - Defn; Args.Len = c - Last; while (Last > Defn && IsWhiteSpace(Last[-1])) Last--; while (Last > Defn && IsFuncNameChar(Last[-1])) Last--; Return.Start = 0; Return.Len = Last - Defn; Name.Start = Last - Defn; Name.Len = Args.Start - Name.Start; if (Name.Len == 0 || Args.Len == 0) { /* LgiTrace("%s:%i - Fn parse empty section '%s' (%i,%i,%i)\n", _FL, Defn, (int)Return.Len, (int)Name.Len, (int)Args.Len); */ return false; } return true; } bool SeekPtr(char16 *&s, char16 *end, int &Line) { if (s > end) { LAssert(0); return false; } while (s < end) { if (*s == '\n') Line++; s++; } return true; } DefnType GuessDefnType(char16 *def, bool debug) { // In the context of a class member, this could be a variable defn: // // bool myVar = true; // bool myVar = someFn(); // // or a function defn: // // bool myFunction(int someArg = 10); // // Try to guess which it is: int roundDepth = 0; int equalsAtZero = 0; for (auto s = def; *s; s++) { if (*s == '(') roundDepth++; else if (*s == ')') roundDepth--; else if (*s == '=') { if (roundDepth == 0) equalsAtZero++; } } /* if (equalsAtZero && debug) printf("equalsAtZero: %S\n", def); */ return equalsAtZero ? DefnVariable : DefnFunc; } bool BuildCppDefnList(const char *FileName, char16 *Cpp, LArray &Defns, int LimitTo, bool Debug) { if (!Cpp) return false; static char16 StrClass[] = {'c', 'l', 'a', 's', 's', 0}; static char16 StrStruct[] = {'s', 't', 'r', 'u', 'c', 't', 0}; static char16 StrEnum[] = {'e', 'n', 'u', 'm', 0}; static char16 StrOpenBracket[] = {'{', 0}; static char16 StrColon[] = {':', 0}; static char16 StrSemiColon[] = {';', 0}; static char16 StrDefine[] = {'d', 'e', 'f', 'i', 'n', 'e', 0}; static char16 StrExtern[] = {'e', 'x', 't', 'e', 'r', 'n', 0}; static char16 StrTypedef[] = {'t', 'y', 'p', 'e', 'd', 'e', 'f', 0}; static char16 StrC[] = {'\"', 'C', '\"', 0}; static char16 StrHash[] = {'#', 0}; char WhiteSpace[] = " \t\r\n"; char16 *s = Cpp; char16 *LastDecl = s; int Depth = 0; int Line = 0; #ifdef DEBUG_LINE int PrevLine = 0; #endif int CaptureLevel = 0; int InClass = false; // true if we're in a class definition char16 *CurClassDecl = 0; bool IsEnum = 0, IsClass = false, IsStruct = false; bool FnEmit = false; // don't emit functions between a f(n) and the next '{' // they are only parent class initializers LArray ConditionalIndex; int ConditionalDepth = 0; bool ConditionalFirst = true; bool ConditionParsingErr = false; #ifdef DEBUG_FILE Debug |= FileName && stristr(FileName, DEBUG_FILE) != NULL; #endif while (s && *s) { // skip ws while (*s && strchr(" \t\r", *s)) s++; #ifdef DEBUG_LINE if (Debug) { if (Line >= DEBUG_LINE - 1) { int asd=0; } else if (PrevLine != Line) { PrevLine = Line; // LgiTrace("%s:%i '%.10S'\n", FileName, Line + 1, s); } } #endif // tackle decl switch (*s) { case 0: break; case '\n': { Line ++; s++; break; } case '#': { const char16 *Hash = s; s++; skipws(s) const char16 *End = s; while (*End && IsAlpha(*End)) End++; if ( ( LimitTo == DefnNone || (LimitTo & DefnDefine) != 0 ) && (End - s) == 6 && StrncmpW(StrDefine, s, 6) == 0 ) { s += 6; defnskipws(s); LexCpp(s, LexNoReturn); char16 r = *s; *s = 0; Defns.New().Set(DefnDefine, FileName, Hash, Line + 1); *s = r; } char16 *Eol = Strchr(s, '\n'); if (!Eol) Eol = s + Strlen(s); // bool IsIf = false, IsElse = false, IsElseIf = false; if ( ((End - s) == 2 && !Strncmp(L"if", s, End - s)) || ((End - s) == 5 && !Strncmp(L"ifdef", s, End - s)) || ((End - s) == 6 && !Strncmp(L"ifndef", s, End - s)) ) { ConditionalIndex[ConditionalDepth] = 1; ConditionalDepth++; ConditionalFirst = IsFirst(ConditionalIndex, ConditionalDepth); if (Debug) LgiTrace("%s:%i - ConditionalDepth++=%i Line=%i: %.*S\n", _FL, ConditionalDepth, Line+1, Eol - s + 1, s - 1); } else if ( ((End - s) == 4 && !Strncmp(L"else", s, End - s)) || ((End - s) == 7 && !Strncmp(L"else if", s, 7)) ) { if (ConditionalDepth <= 0 && !ConditionParsingErr) { ConditionParsingErr = true; LgiTrace("%s:%i - Error parsing pre-processor conditions: %s:%i\n", _FL, FileName, Line+1); } if (ConditionalDepth > 0) { ConditionalIndex[ConditionalDepth-1]++; ConditionalFirst = IsFirst(ConditionalIndex, ConditionalDepth); if (Debug) LgiTrace("%s:%i - ConditionalDepth=%i Idx++ Line=%i: %.*S\n", _FL, ConditionalDepth, Line+1, Eol - s + 1, s - 1); } } else if ( ((End - s) == 5 && !Strncmp(L"endif", s, End - s)) ) { if (ConditionalDepth <= 0 && !ConditionParsingErr) { ConditionParsingErr = true; LgiTrace("%s:%i - Error parsing pre-processor conditions: %s:%i\n", _FL, FileName, Line+1); } if (ConditionalDepth > 0) ConditionalDepth--; ConditionalFirst = IsFirst(ConditionalIndex, ConditionalDepth); if (Debug) LgiTrace("%s:%i - ConditionalDepth--=%i Line=%i: %.*S\n", _FL, ConditionalDepth, Line+1, Eol - s + 1, s - 1); } while (*s) { if (*s == '\n') { // could be end of # command char Last = (s[-1] == '\r') ? s[-2] : s[-1]; if (Last != '\\') break; Line++; } s++; LastDecl = s; } break; } case '\"': case '\'': { char16 Delim = *s; s++; while (*s) { if (*s == Delim) { s++; break; } if (*s == '\\') s++; if (*s == '\n') Line++; s++; } break; } case '{': { s++; if (ConditionalFirst) Depth++; FnEmit = false; #ifdef DEBUG_FILE if (Debug) LgiTrace("%s:%i - FnEmit=%i Depth=%i @ line %i\n", _FL, FnEmit, Depth, Line+1); #endif break; } case '}': { s++; if (ConditionalFirst) { if (Depth > 0) { Depth--; #ifdef DEBUG_FILE if (Debug) LgiTrace("%s:%i - CaptureLevel=%i Depth=%i @ line %i\n", _FL, CaptureLevel, Depth, Line+1); #endif } else { #ifdef DEBUG_FILE if (Debug) LgiTrace("%s:%i - ERROR Depth already=%i @ line %i\n", _FL, Depth, Line+1); #endif } } defnskipws(s); LastDecl = s; if (IsEnum) { if (IsAlpha(*s)) // typedef'd enum? { LAutoWString t(LexCpp(s, LexStrdup)); if (t) Defns.New().Set(DefnEnum, FileName, t.Get(), Line + 1); } IsEnum = false; } break; } case ';': { s++; LastDecl = s; if (Depth == 0 && InClass) { // Check for typedef struct name char16 *Start = s - 1; while (Start > Cpp && Start[-1] != '}') Start--; LString TypeDef = LString(Start, s - Start - 1).Strip(); if (TypeDef.Length() > 0) { if (LimitTo == DefnNone || (LimitTo & DefnClass) != 0) { Defns.New().Set(DefnClass, FileName, TypeDef, Line + 1); } } // End the class def InClass = false; CaptureLevel = 0; #ifdef DEBUG_FILE if (Debug) LgiTrace("%s:%i - CaptureLevel=%i Depth=%i @ line %i\n", _FL, CaptureLevel, Depth, Line+1); #endif DeleteArray(CurClassDecl); } break; } case '/': { s++; if (*s == '/') { // one line comment while (*s && *s != '\n') s++; LastDecl = s; } else if (*s == '*') { // multi line comment s++; while (*s) { if (s[0] == '*' && s[1] == '/') { s += 2; break; } if (*s == '\n') Line++; s++; } LastDecl = s; } break; } case '(': { s++; if (Depth == CaptureLevel && !FnEmit && LastDecl && ConditionalFirst) { // function? // find start: char16 *Start = LastDecl; skipws(Start); if (Strnstr(Start, L"__attribute__", s - Start)) break; // find end (matching closing bracket) int RoundDepth = 1; char16 *End = s; while (*End) { if (*End == '/' && End[1] == '/') { End = Strchr(End, '\n'); if (!End) break; } if (*End == '(') { RoundDepth++; } else if (*End == ')') { if (--RoundDepth == 0) break; } End++; } if (End && *End) { End++; auto Buf = NewStrW(Start, End-Start); if (Buf) { // remove new-lines auto Out = Buf; for (auto In = Buf; *In; In++) { if (*In == '\r' || *In == '\n' || *In == '\t' || *In == ' ') { *Out++ = ' '; skipws(In); In--; } else if (In[0] == '/' && In[1] == '/') { In = StrchrW(In, '\n'); if (!In) break; } else { *Out++ = *In; } } *Out++ = 0; if (CurClassDecl) { char16 Str[1024]; ZeroObj(Str); StrncpyW(Str, Buf, CountOf(Str)); char16 *b = StrchrW(Str, '('); if (b) { // Skip whitespace between name and '(' while ( b > Str && strchr(WhiteSpace, b[-1]) ) { b--; } // Skip over to the start of the name.. while ( b > Str && b[-1] != '*' && b[-1] != '&' && !strchr(WhiteSpace, b[-1]) ) { b--; } auto ClsLen = StrlenW(CurClassDecl); auto BLen = StrlenW(b); if (ClsLen + BLen + 1 > CountOf(Str)) { LgiTrace("%s:%i - Defn too long: %i\n", _FL, ClsLen + BLen + 1); } else { memmove(b + ClsLen + 2, b, sizeof(*b) * (BLen+1)); memcpy(b, CurClassDecl, sizeof(*b) * ClsLen); b += ClsLen; *b++ = ':'; *b++ = ':'; DeleteArray(Buf); Buf = NewStrW(Str); } } } // cache f(n) def auto Type = GuessDefnType(Buf, Debug); if ( ( LimitTo == DefnNone || (LimitTo & Type) != 0 ) && *Buf != ')' ) Defns.New().Set(Type, FileName, Buf, Line + 1); DeleteArray(Buf); while (*End && !strchr(";:{#", *End)) End++; SeekPtr(s, End, Line); FnEmit = *End != ';'; #if 0 // def DEBUG_FILE if (Debug) LgiTrace("%s:%i - FnEmit=%i Depth=%i @ line %i\n", _FL, FnEmit, Depth, Line+1); #endif } } } else { #ifdef DEBUG_FILE if (Debug) LgiTrace("%s:%i - Not attempting fn parse: depth=%i, capture=%i, fnEmit=%i, CondFirst=%i, %s:%i:%.20S\n", _FL, Depth, CaptureLevel, FnEmit, ConditionalFirst, LGetLeaf(FileName), Line, s-1); #endif } break; } default: { bool InTypedef = false; if (IsAlpha(*s) || IsDigit(*s) || *s == '_') { char16 *Start = s; s++; defnskipsym(s); auto TokLen = s - Start; if (TokLen == 6 && StrncmpW(StrExtern, Start, 6) == 0) { // extern "C" block char16 *t = LexCpp(s, LexStrdup); // "C" if (t && StrcmpW(t, StrC) == 0) { defnskipws(s); if (*s == '{') { Depth--; } } DeleteArray(t); } else if (TokLen == 7 && StrncmpW(StrTypedef, Start, 7) == 0) { // Typedef skipws(s); IsStruct = !Strnicmp(StrStruct, s, StrlenW(StrStruct)); IsClass = !Strnicmp(StrClass, s, StrlenW(StrClass)); if (IsStruct || IsClass) { Start = s; InTypedef = true; goto DefineStructClass; } IsEnum = !Strnicmp(StrEnum, s, StrlenW(StrEnum)); if (IsEnum) { Start = s; s += 4; goto DefineEnum; } LStringPipe p; char16 *i; for (i = Start; i && *i;) { switch (*i) { case ' ': case '\t': { p.Push(Start, i - Start); defnskipws(i); char16 sp[] = {' ', 0}; p.Push(sp); Start = i; break; } case '\n': Line++; // fall thru case '\r': { p.Push(Start, i - Start); i++; Start = i; break; } case '{': { p.Push(Start, i - Start); int Depth = 1; i++; while (*i && Depth > 0) { switch (*i) { case '{': Depth++; break; case '}': Depth--; break; case '\n': Line++; break; } i++; } Start = i; break; } case ';': { p.Push(Start, i - Start); s = i; i = 0; break; } default: { i++; break; } } } char16 *Typedef = p.NewStrW(); if (Typedef) { if (LimitTo == DefnNone || (LimitTo & DefnTypedef) != 0) { Defns.New().Set(DefnTypedef, FileName, Typedef, Line + 1); } DeleteArray(Typedef); } } else if ( ( TokLen == 5 && (IsClass = !StrncmpW(StrClass, Start, 5)) ) || ( TokLen == 6 && (IsStruct = !StrncmpW(StrStruct, Start, 6)) ) ) { DefineStructClass: // Class / Struct if (Depth == 0) { // Check if this is really a class/struct definition or just a reference char16 *next = s; while (*next) { // If we seek to the next line, check it's not a preprocessor directive. if (*next == '\n') { next++; while (*next && strchr(WhiteSpace, *next)) next++; if (*next == '#') { // Skip the processor line... while (*next && *next != '\n') next++; } continue; } else if (strchr(";(){", *next)) break; next++; } if (*next == '{') { // Full definition InClass = true; CaptureLevel = 1; #ifdef DEBUG_FILE if (Debug) LgiTrace("%s:%i - CLASS/STRUCT defn: CaptureLevel=%i Depth=%i @ line %i\n", _FL, CaptureLevel, Depth, Line+1); #endif - char16 *n = Start + (IsClass ? StrlenW(StrClass) : StrlenW(StrStruct)), *t; + char16 *n = Start, *t; List Tok; + while (IsAlpha(*n)) + n++; + while (n && *n) { char16 *Last = n; if ((t = LexCpp(n, LexStrdup))) { #ifdef DEBUG_FILE if (Debug) LgiTrace(" t='%S' @ line %i\n", t, Line+1); #endif if (StrcmpW(t, StrSemiColon) == 0) { DeleteArray(t); break; } else if (StrcmpW(t, L"LgiClass") == 0 || StrcmpW(t, L"ScribeClass") == 0) { // Ignore these... DeleteArray(t); } else if (StrcmpW(t, StrHash) || StrcmpW(t, StrOpenBracket) == 0 || StrcmpW(t, StrColon) == 0) { DeleteArray(CurClassDecl); if (Tok.Length() > 0) { CurClassDecl = *Tok.rbegin(); Tok.Delete(CurClassDecl); } else { CurClassDecl = t; t = NULL; } if (LimitTo == DefnNone || (LimitTo & DefnClass) != 0) { char16 r = *Last; *Last = 0; Defns.New().Set(DefnClass, FileName, Start, Line + 1); *Last = r; SeekPtr(s, next, Line); } #ifdef DEBUG_FILE if (Debug) LgiTrace(" class='%S' @ line %i\n", CurClassDecl, Line+1); #endif DeleteArray(t); break; } else { Tok.Insert(t); } } else { LgiTrace("%s:%i - LexCpp failed at %s:%i.\n", _FL, FileName, Line+1); break; } } Tok.DeleteArrays(); } else if (InTypedef) { // Typedef'ing some other structure... // char16 *Start = s; LexCpp(s, LexNoReturn); defnskipws(s); LArray a; char16 *t; ssize_t StartRd = -1, EndRd = -1; while ((t = LexCpp(s, LexStrdup))) { if (StartRd < 0 && !StrcmpW(t, L"(")) StartRd = a.Length(); else if (EndRd < 0 && !StrcmpW(t, L")")) EndRd = a.Length(); if (!StrcmpW(t, StrSemiColon)) break; a.Add(t); } if (a.Length()) { auto iName = StartRd > 0 && EndRd > StartRd ? StartRd - 1 : a.Length() - 1; auto sName = a[iName]; Defns.New().Set(DefnTypedef, FileName, sName, Line + 1); a.DeleteArrays(); } } } } else if ( TokLen == 4 && StrncmpW(StrEnum, Start, 4) == 0 ) { DefineEnum: IsEnum = true; defnskipws(s); LAutoWString t(LexCpp(s, LexStrdup)); if (t && isalpha(*t)) { Defns.New().Set(DefnEnum, FileName, t.Get(), Line + 1); } } else if (IsEnum) { char16 r = *s; *s = 0; Defns.New().Set(DefnEnumValue, FileName, Start, Line + 1); *s = r; defnskipws(s); if (*s == '=') { s++; while (true) { defnskipws(s); defnskipsym(s); defnskipws(s); if (*s == 0 || *s == ',' || *s == '}') break; LAssert(*s != '\n'); s++; } } } if (s[-1] == ':') { LastDecl = s; } } else { s++; } break; } } } DeleteArray(CurClassDecl); if (Debug) { for (unsigned i=0; iType), def->File.Get(), def->Line, def->Name.Get()); } } return Defns.Length() > 0; } diff --git a/src/common/Coding/ScriptCompiler.cpp b/src/common/Coding/ScriptCompiler.cpp --- a/src/common/Coding/ScriptCompiler.cpp +++ b/src/common/Coding/ScriptCompiler.cpp @@ -1,3866 +1,3869 @@ /// \file #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/LexCpp.h" #include "lgi/common/LgiString.h" #include "ScriptingPriv.h" // #define DEBUG_SCRIPT_FILE "Mail Filters Menu.script" #define GetTok(c) ((c) < Tokens.Length() ? Tokens[c] : NULL) #define GetTokType(c) ((c) < Tokens.Length() ? ExpTok.Find(Tokens[c]) : TNone) #define GV_VARIANT GV_MAX const char *sDebugger = "Debugger"; int LFunctionInfo::_Infos = 0; enum LTokenType { #define _(type, str) T##type, #include "TokenType.h" #undef _ }; struct LinkFixup { int Tok; size_t Offset; int Args; LFunctionInfo *Func; }; struct Node { typedef LArray NodeExp; struct VariablePart { LVariant Name; NodeExp Array; bool Call; LArray Args; VariablePart() { Call = false; } ~VariablePart() { Args.DeleteObjects(); } }; // Hierarchy NodeExp Child; // One of the following are valid: LOperator Op = OpNull; // -or- bool Constant = false; int Tok = -1; LArray Lst; LTokenType ConstTok = TNone; // -or- LFunc *ContextFunc = NULL; LArray Args; // -or- LFunctionInfo *ScriptFunc = NULL; // -or- LArray Variable; // Used during building LVarRef Reg; LVarRef ArrayIdx; void Init() { Reg.Empty(); ArrayIdx.Empty(); } void SetOp(LOperator o, int t) { Init(); Op = o; Tok = t; } void SetConst(int t, LTokenType e) { Init(); Constant = true; Tok = t; ConstTok = e; } void SetConst(LArray &list_tokens, LTokenType e) { Init(); Constant = true; Lst = list_tokens; ConstTok = e; } void SetContextFunction(LFunc *m, int tok) { Init(); ContextFunc = m; Tok = tok; } void SetScriptFunction(LFunctionInfo *m, int tok) { Init(); ScriptFunc = m; Tok = tok; } void SetVar(char16 *Var, int t) { Init(); Variable[0].Name = Var; Tok = t; } bool IsVar() { return Variable.Length() > 0; } bool IsContextFunc() { return ContextFunc != 0; } bool IsScriptFunc() { return ScriptFunc != 0; } bool IsConst() { return Constant; } }; LCompiledCode::LCompiledCode() : Globals(SCOPE_GLOBAL), Debug(0, -1) { SysContext = NULL; UserContext = NULL; } LCompiledCode::LCompiledCode(LCompiledCode ©) : Globals(SCOPE_GLOBAL), Debug(0, -1) { *this = copy; } LCompiledCode::~LCompiledCode() { for (auto e: Externs) e->InUse = false; Externs.DeleteObjects(); } LCompiledCode &LCompiledCode::operator =(const LCompiledCode &c) { Globals = c.Globals; ByteCode = c.ByteCode; Types = c.Types; Debug = c.Debug; Methods = c.Methods; FileName = c.FileName; Source = c.Source; return *this; } LFunctionInfo *LCompiledCode::GetMethod(const char *Name, bool Create) { for (auto &m: Methods) { const char *Fn = m->GetName(); if (!strcmp(Fn, Name)) { return m; } } if (Create) { LAutoRefPtr n(new LFunctionInfo(Name)); if (n) { Methods.Add(n); return n; } } return 0; } int LCompiledCode::ObjectToSourceAddress(size_t ObjAddr) { auto Idx = Debug.Find(ObjAddr); return Idx < 0 ? 1 : Idx; } const char *LCompiledCode::AddrToSourceRef(size_t ObjAddr) { static char Status[256]; size_t Addr = ObjAddr; int LineNum = ObjectToSourceAddress(Addr); // Search for the start of the instruction... while (Addr > 0 && LineNum < 0) LineNum = ObjectToSourceAddress(--Addr); char *Dir = FileName ? strrchr(FileName, DIR_CHAR) : NULL; size_t PathLen = Dir ? Dir - FileName : 0; LString FileRef = FileName ? (PathLen > 24 ? Dir + 1 : FileName.Get()) : "(untitled)"; if (LineNum >= 0) sprintf_s( Status, sizeof(Status), "%s:%i", FileRef.Get(), LineNum); else sprintf_s(Status, sizeof(Status), "%s:0x%x", FileRef.Get(), (int)ObjAddr); return Status; } LVariant *LCompiledCode::Set(const char *Name, LVariant &v) { int i = Globals.Var(Name, true); if (i >= 0) { Globals[i] = v; return &Globals[i]; } return 0; } template void UnEscape(T *t) { T *i = t, *o = t; while (*i) { if (*i == '\\') { i++; switch (*i) { case '\\': *o++ = '\\'; i++; break; case 'n': *o++ = '\n'; i++; break; case 'r': *o++ = '\r'; i++; break; case 't': *o++ = '\t'; i++; break; case 'b': *o++ = '\b'; i++; break; } } else *o++ = *i++; } *o++ = 0; } class TokenRanges { LArray FileNames; struct Range { int Start, End; ssize_t File; int Line; }; LArray Ranges; char fl[MAX_PATH_LEN + 32]; public: TokenRanges() { Empty(); } void Empty() { Ranges.Length(0); } ssize_t Length() { return Ranges.Length(); } /// Gets the file/line at a given token const char *operator [](ssize_t Tok) { Range *r = NULL; for (unsigned i=0; i= rng->Start && Tok <= rng->End) { r = rng; break; } } if (!r) r = &Ranges.Last(); if (r->File >= (ssize_t)FileNames.Length()) { LAssert(!"Invalid file index."); return "#err: invalid file index"; } else { sprintf_s(fl, sizeof(fl), "%s:%i", FileNames[r->File].Get(), r->Line); } return fl; } const char *GetLine(int Line) { if (Ranges.Length() > 0) { Range &r = Ranges.Last(); if (r.File < (ssize_t)FileNames.Length()) sprintf_s(fl, sizeof(fl), "%s:%i", FileNames[r.File].Get(), Line); else return "#err: invalid file index."; } else { sprintf_s(fl, sizeof(fl), "$unknown:%i", Line); } return fl; } ssize_t GetFileIndex(const char *FileName) { for (unsigned i=0; iFile != FileId || r->Line != Line) { // Start a new range... r = &Ranges.New(); r->Start = r->End = TokIndex; r->File = FileId; r->Line = Line; } else { r->End = TokIndex; } } }; /// Scripting language compiler implementation class LCompilerPriv : public LCompileTools, public LScriptUtils { LHashTbl, LVariantType> Types; size_t JumpLoc; LArray> FuncMem; public: LScriptContext *SysCtx; LScriptContext *UserCtx; LCompiledCode *Code; LStream *Log; LArray Tokens; TokenRanges Lines; char16 *Script; LHashTbl, LFunc*> Methods; uint64_t Regs; LArray Scopes; LArray Fixups; LHashTbl, char16*> Defines; LHashTbl, LTokenType> ExpTok; LDom *ScriptArgs; LVarRef ScriptArgsRef; bool ErrShowFirstOnly; LArray ErrLog; bool Debug; #ifdef _DEBUG LString::Array RegAllocators; #endif LCompilerPriv() { Debug = false; ErrShowFirstOnly = true; SysCtx = NULL; UserCtx = NULL; Code = 0; Log = 0; Script = 0; Regs = 0; ScriptArgs = NULL; ScriptArgsRef.Empty(); #define LNULL NULL #define _(type, str) if (str) ExpTok.Add(L##str, T##type); #include "TokenType.h" #undef _ #undef LNULL Types.Add("int", GV_INT32); Types.Add("short", GV_INT32); Types.Add("long", GV_INT32); Types.Add("int8", GV_INT32); Types.Add("int16", GV_INT32); Types.Add("int32", GV_INT32); Types.Add("int64", GV_INT64); Types.Add("uint8", GV_INT32); Types.Add("uint16", GV_INT32); Types.Add("uint32", GV_INT32); Types.Add("uint64", GV_INT64); Types.Add("bool", GV_BOOL); Types.Add("boolean", GV_BOOL); Types.Add("double", GV_DOUBLE); Types.Add("float", GV_DOUBLE); Types.Add("char", GV_STRING); Types.Add("LDom", GV_DOM); Types.Add("void", GV_VOID_PTR); Types.Add("LDateTime", GV_DATETIME); Types.Add("GHashTable", GV_HASHTABLE); Types.Add("LOperator", GV_OPERATOR); Types.Add("LView", GV_GVIEW); Types.Add("LMouse", GV_LMOUSE); Types.Add("LKey", GV_LKEY); Types.Add("LVariant", GV_VARIANT); // Types.Add("binary", GV_BINARY); // Types.Add("List", GV_LIST); // Types.Add("LDom&", GV_DOMREF); } ~LCompilerPriv() { Empty(); } void Empty() { SysCtx = NULL; UserCtx = NULL; DeleteObj(Code); Log = 0; Tokens.DeleteArrays(); Lines.Empty(); DeleteArray(Script); Defines.DeleteArrays(); Methods.Empty(); } /// Prints the error message bool OnError ( /// The line number (less than 0) or Token index (greater than 0) int LineOrTok, /// The format for the string (printf) const char *Msg, /// Variable argument list ... ) { if (!ErrShowFirstOnly || ErrLog.Length() == 0) { char Buf[512]; va_list Arg; va_start(Arg, Msg); #ifndef WIN32 #define _vsnprintf vsnprintf #endif vsprintf_s(Buf, sizeof(Buf), Msg, Arg); Log->Print("%s - Error: %s\n", LineOrTok < 0 ? Lines.GetLine(-LineOrTok) : Lines[LineOrTok], Buf); va_end(Arg); ErrLog.New() = Buf; } return false; // Always return false to previous caller } void DebugInfo(int Tok) { const char *Line = Lines[Tok]; if (Line) { const char *c = strrchr(Line, ':'); if (c) { Code->Debug.Add(Code->ByteCode.Length(), ::atoi(c+1)); } } } /// Assemble a zero argument instruction bool Asm0(int Tok, uint8_t Op) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1)) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; } else return false; return true; } /// Assemble one arg instruction bool Asm1(int Tok, uint8_t Op, LVarRef a) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 5)) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; *p.r++ = a; } else return false; return true; } /// Assemble two arg instruction bool Asm2(int Tok, uint8_t Op, LVarRef a, LVarRef b) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 9)) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; *p.r++ = a; *p.r++ = b; } else return false; return true; } /// Assemble three arg instruction bool Asm3(int Tok, uint8_t Op, LVarRef a, LVarRef b, LVarRef c) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1 + (sizeof(LVarRef) * 3) )) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; *p.r++ = a; *p.r++ = b; *p.r++ = c; } else return false; return true; } /// Assemble four arg instruction bool Asm4(int Tok, uint8_t Op, LVarRef a, LVarRef b, LVarRef c, LVarRef d) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1 + (sizeof(LVarRef) * 4) )) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; if (!a.Valid()) AllocNull(a); *p.r++ = a; if (!b.Valid()) AllocNull(b); *p.r++ = b; if (!c.Valid()) AllocNull(c); *p.r++ = c; if (!d.Valid()) AllocNull(d); *p.r++ = d; } else return false; return true; } /// Assemble 'n' length arg instruction bool AsmN(int Tok, uint8_t Op, LArray &Args) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1 + (sizeof(LVarRef) * Args.Length()) )) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; for (unsigned i=0; iDefines.Find(wName); if (v) { // Is it a number? Cause we should really convert to an int if possible... #define INVALID_CONVERSION -11111 int64 i = INVALID_CONVERSION; if (!Strnicmp(v, L"0x", 2)) i = Atoi(v, 16, INVALID_CONVERSION); else if (IsDigit(*v) || strchr("-.", *v)) i = Atoi(v); if (i != INVALID_CONVERSION) Value = i; else Value = v; return true; } return false; } }; /// This will look at resolving the expression to something simpler before storing it... void SimplifyDefine(LScriptEngine &Eng, char16 *Name, LAutoWString &Value) { LString Exp = Value.Get(); if (Exp.Find("(") >= 0) // Filter out simple expressions... { LVariant Result; CompilerDefineSource Src(this); if (Eng.EvaluateExpression(&Result, &Src, Exp)) { auto Val = Result.CastString(); if (Val) { // LgiTrace("Simplify: %S -> %s\n", Value.Get(), Val); Value.Reset(Utf8ToWide(Val)); } } } } /// Convert the source from one big string into an array of tokens bool Lex(char *Source, const char *FileName) { char16 *w = Utf8ToWide(Source); if (!w) return OnError(0, "Couldn't convert source to wide chars."); LScriptEngine Eng(NULL, NULL, NULL); ssize_t FileIndex = Lines.GetFileIndex(FileName); int Line = 1; char16 *s = w, *t; while ((t = LexCpp(s, LexStrdup, NULL, &Line))) { if (*t == '#') { ssize_t Len; if (!StrnicmpW(t + 1, sInclude, Len = StrlenW(sInclude))) { LAutoWString Raw(LexCpp(s, LexStrdup)); LAutoWString File(TrimStrW(Raw, (char16*)L"\"\'")); if (File) { LVariant v; LString IncCode; v.OwnStr(File.Release()); if (UserCtx) { IncCode = UserCtx->GetIncludeFile(v.Str()); if (IncCode) { Lex(IncCode, v.Str()); } else { DeleteArray(t); return OnError(-Line, "Ctx failed to include '%s'", v.Str()); } } else { if (LIsRelativePath(v.Str())) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), FileName, ".."); LMakePath(p, sizeof(p), p, v.Str()); v = p; } if (LFileExists(v.Str())) { LFile f(v.Str()); if (f && (IncCode = f.Read())) { Lex(IncCode, v.Str()); } else { DeleteArray(t); return OnError(-Line, "Couldn't read '%s'", v.Str()); } } else { DeleteArray(t); return OnError(-Line, "Couldn't include '%s'", v.Str()); } } } else { OnError(-Line, "No file for #include."); } } else if (!StrnicmpW(t + 1, sDefine, Len = StrlenW(sDefine))) { LAutoWString Name(LexCpp(s, LexStrdup)); if (Name && IsAlpha(*Name)) { char16 *Start = s; while (*Start && strchr(WhiteSpace, *Start)) Start++; char16 *Eol = StrchrW(Start, '\n'); if (!Eol) Eol = Start + StrlenW(Start); while (Eol > Start && strchr(WhiteSpace, Eol[-1])) Eol--; LAutoWString Value(NewStrW(Start, Eol - Start)); SimplifyDefine(Eng, Name, Value); Defines.Add(Name, Value.Release()); s = Eol; } } DeleteArray(t); continue; } if (!ResolveDefine(t, FileIndex, Line)) { Lines.Add((int)Tokens.Length(), FileIndex, Line); Tokens.Add(t); } } // end of "while (t = LexCpp)" loop if (!Script) { Script = w; } else { DeleteArray(w); } return true; } /// Create a null var ref void AllocNull(LVarRef &r) { r.Scope = SCOPE_GLOBAL; if (Code->Globals.NullIndex < 0) Code->Globals.NullIndex = (int)Code->Globals.Length(); r.Index = Code->Globals.NullIndex; Code->Globals[r.Index].Type = GV_NULL; } /// Allocate a variant and ref LVariant *PreAllocVariant(LVarRef &r) { r.Scope = SCOPE_GLOBAL; r.Index = (int)Code->Globals.Length(); return &Code->Globals[r.Index]; } /// Allocate a constant double void AllocConst(LVarRef &r, double d) { r.Scope = SCOPE_GLOBAL; if (Code->Globals.Length()) { // Check for existing int LVariant *p = &Code->Globals[0]; LVariant *e = p + Code->Globals.Length(); while (p < e) { if (p->Type == GV_DOUBLE && p->Value.Dbl == d) { r.Index = (int) (p - &Code->Globals[0]); return; } p++; } } r.Index = (int)Code->Globals.Length(); Code->Globals[r.Index] = d; } /// Allocate a constant bool void AllocConst(LVarRef &r, bool b) { r.Scope = SCOPE_GLOBAL; if (Code->Globals.Length()) { // Check for existing int LVariant *p = &Code->Globals[0]; LVariant *e = p + Code->Globals.Length(); while (p < e) { if (p->Type == GV_BOOL && p->Value.Bool == b) { r.Index = (int)(p - &Code->Globals[0]); return; } p++; } } r.Index = (int)Code->Globals.Length(); Code->Globals[r.Index] = b; } /// Allocate a constant int void AllocConst(LVarRef &r, int64 i) { r.Scope = SCOPE_GLOBAL; LVariantType Type = i <= INT_MAX && i >= INT_MIN ? GV_INT32 : GV_INT64; if (Code->Globals.Length()) { // Check for existing int LVariant *p = &Code->Globals[0]; LVariant *e = p + Code->Globals.Length(); while (p < e) { if (p->Type == Type) { if (Type == GV_INT32 && p->Value.Int == i) { r.Index = (int) (p - &Code->Globals[0]); return; } else if (Type == GV_INT64 && p->Value.Int64 == i) { r.Index = (int) (p - &Code->Globals[0]); return; } } p++; } } // Allocate new global r.Index = (int)Code->Globals.Length(); if (Type == GV_INT32) Code->Globals[r.Index] = (int32)i; else Code->Globals[r.Index] = i; } void AllocConst(LVarRef &r, int i) { AllocConst(r, (int64)i); } /// Allocate a constant string void AllocConst(LVarRef &r, char *s, ssize_t len = -1) { LAssert(s != 0); if (len < 0) len = strlen(s); r.Scope = SCOPE_GLOBAL; r.Index = (int)Code->Globals.Length(); for (unsigned i=0; iGlobals.Length(); i++) { if (Code->Globals[i].Type == GV_STRING) { char *c = Code->Globals[i].Str(); if (*s == *c && strcmp(s, c) == 0) { r.Index = i; return; } } } LVariant &v = Code->Globals[r.Index]; v.Type = GV_STRING; if ((v.Value.String = NewStr(s, len))) { UnEscape(v.Value.String); } } /// Allocate a constant wide string void AllocConst(LVarRef &r, char16 *s, ssize_t len) { LAssert(s != 0); char *utf = WideToUtf8(s, len); if (!utf) utf = NewStr(""); r.Scope = SCOPE_GLOBAL; r.Index = (int)Code->Globals.Length(); for (unsigned i=0; iGlobals.Length(); i++) { if (Code->Globals[i].Type == GV_STRING) { char *c = Code->Globals[i].Str(); if (*utf == *c && strcmp(utf, c) == 0) { r.Index = i; DeleteArray(utf); return; } } } LVariant &v = Code->Globals[r.Index]; v.Type = GV_STRING; if ((v.Value.String = utf)) { UnEscape(v.Value.String); } } /// Find a variable by name, creating it if needed LVarRef FindVariable(LVariant &Name, bool Create) { LVarRef r = {0, -1}; // Look for existing variable... ssize_t i; for (i=Scopes.Length()-1; i>=0; i--) { r.Index = Scopes[i]->Var(Name.Str(), false); if (r.Index >= 0) { r.Scope = Scopes[i]->Scope; return r; } } // Create new variable on most recent scope i = Scopes.Length() - 1; r.Index = Scopes[i]->Var(Name.Str(), Create); if (r.Index >= 0) { r.Scope = Scopes[i]->Scope; } return r; } /// Build asm to assign a var ref bool AssignVarRef(Node &n, LVarRef &Value) { /* Examples and what their assembly should look like: a = Value Assign a <- Value a[b] = Value R0 = AsmExpression(b); ArraySet a[R0] <- Value a[b].c = Value R0 = AsmExpression(b); R0 = ArrayGet a[R0] DomSet(R0, "c", Null, Value) a[b].c[d] R0 = AsmExpression(b); R0 = ArrayGet a[R0]; R1 = AsmExpression(d); R0 = DomGet(R0, "c", R1); ArraySet R0[R1] = Value a.b[c].d = Value R1 = AsmExpression(c); R0 = DomGet(a, "b", R1); DomSet(R1, "d", Null, Value) // resolve initial array if (parts > 1 && part[0].array) { // cur = ArrayGet(part[0].var, part[0].array) } else { cur = part[0] } // dom loop over loop (p = 0 to parts - 2) { if (part[p+1].array) arr = exp(part[p+1].array) else arr = null cur = DomGet(cur, part[p+1].var, arr) } // final part if (part[parts-1].arr) { arr = exp(part[parts-1].arr) ArraySet(cur, arr) } else { DomSet(cur, part[parts-1].var, null, value) } */ if (!Value.Valid()) { LAssert(!"Invalid value to assign.\n"); return false; } if (!n.IsVar()) { LAssert(!"Target must be a variable."); return false; } // Gets the first part of the variable. LVarRef Cur = FindVariable(n.Variable[0].Name, true); if (!Cur.Valid()) return false; if (n.Variable.Length() > 1) { // Do any initial array dereference if (n.Variable[0].Array.Length()) { // Assemble the array index's expression into 'Idx' LVarRef Idx; Idx.Empty(); if (!AsmExpression(&Idx, n.Variable[0].Array)) return OnError(n.Tok, "Error creating bytecode for array index."); LVarRef Dst = Cur; if (!Dst.IsReg()) { AllocReg(Dst, n.Tok, _FL); } // Assemble array load instruction Asm3(n.Tok, IArrayGet, Dst, Cur, Idx); Cur = Dst; // Cleanup DeallocReg(Idx); } // Do all the DOM "get" instructions unsigned p; for (p=0; pOwnStr(NewStrW(t + 1, Len - 2)); } else if (StrchrW(t, '.')) { // double *v = atof(t); } else if (t[0] == '0' && tolower(t[1]) == 'x') { // hex integer *v = htoi(t + 2); } else { // decimal integer int64 i = Atoi(t); if (i <= INT_MAX && i >= INT_MIN) *v = (int32)i; else *v = i; } return true; } /// Convert a token stream to a var ref bool TokenToVarRef(Node &n, LVarRef *&LValue) { if (n.Reg.Valid()) { if (LValue && n.Reg != *LValue) { // Need to assign to LValue LAssert(*LValue != n.Reg); Asm2(n.Tok, IAssign, *LValue, n.Reg); } } else { if (n.IsVar()) { // Variable unsigned p = 0; bool ArrayDeindexed = false; bool HasScriptArgs = Scopes.Length() <= 1 && ScriptArgs != NULL; LVarRef v = FindVariable(n.Variable[p].Name, /*!HasScriptArgs ||*/ LValue != NULL); if (v.Index < 0) { if (HasScriptArgs) { if (AllocReg(v, n.Tok, _FL)) { char16 *VarName = GetTok((unsigned)n.Tok); if (!ScriptArgsRef.Valid()) { // Setup the global variable to address the script argument variable ScriptArgsRef.Scope = SCOPE_GLOBAL; ScriptArgsRef.Index = (int)Code->Globals.Length(); LVariant &v = Code->Globals[ScriptArgsRef.Index]; v.Type = GV_DOM; v.Value.Dom = ScriptArgs; } LVarRef Name, Array; AllocConst(Name, VarName, -1); if (n.Variable[p].Array.Length()) { if (!AsmExpression(&Array, n.Variable[p].Array)) return OnError(n.Tok, "Can't assemble array expression."); ArrayDeindexed = true; } else AllocNull(Array); Asm4(n.Tok, IDomGet, v, ScriptArgsRef, Name, Array); } else return false; } else { Node::VariablePart &vp = n.Variable[p]; return OnError(n.Tok, "Undefined variable: %s", vp.Name.Str()); } } else { if (v.Scope == SCOPE_OBJECT) { // We have to load the variable into a register LVarRef Reg, ThisPtr, MemberIndex, Null; if (!AllocReg(Reg, n.Tok, _FL)) return OnError(n.Tok, "Couldn't alloc register."); ThisPtr.Scope = SCOPE_LOCAL; ThisPtr.Index = 0; AllocConst(MemberIndex, v.Index); AllocNull(Null); Asm4(n.Tok, IDomGet, Reg, ThisPtr, MemberIndex, Null); v = Reg; // Object variable now in 'Reg' } if (n.ConstTok == TTypeId) { if (!v.IsReg()) { // Because we are casting to it's DOM ptr, // make sure it's a register first so we don't lose the // actual variable. LVarRef reg; if (!AllocReg(reg, n.Tok, _FL)) return OnError(n.Tok, "Couldn't alloc register."); LAssert(reg != v); Asm2(n.Tok, IAssign, reg, v); v = reg; } // Casting to DOM will give as access to the type info for a LCustomType. // This currently doesn't work with other types :( Asm1(n.Tok, ICast, v); Code->ByteCode.Add(GV_DOM); } } n.Reg = v; LValue = NULL; LAssert(v.Scope != SCOPE_OBJECT); // Does it have an array deref? if (!ArrayDeindexed && n.Variable[p].Array.Length()) { // Evaluate the array indexing expression if (!AsmExpression(&n.ArrayIdx, n.Variable[p].Array)) { return OnError(n.Tok, "Error creating byte code for array index."); } // Do we need to create code to load the value from the array? LVarRef Val; if (AllocReg(Val, n.Tok, _FL)) { Asm3(n.Tok, IArrayGet, Val, n.Reg, n.ArrayIdx); n.Reg = Val; } else return OnError(n.Tok, "Error allocating register."); } // Load DOM parts... for (++p; p Args; Node::VariablePart &Part = n.Variable[p]; Name.Empty(); Arr.Empty(); char *nm = Part.Name.Str(); AllocConst(Name, nm, strlen(nm)); if (Part.Array.Length()) { if (!AsmExpression(&Arr, Part.Array)) { return OnError(n.Tok, "Can't assemble array expression."); } } else if (Part.Call) { for (unsigned i=0; i Call; Call[0] = Dst; Call[1] = n.Reg; Call[2] = Name; // This must always be a global, the decompiler requires access // to the constant to know how many arguments to print. And it // can only see the globals. AllocConst(Call[3], (int)Args.Length()); Call.Add(Args); AsmN(n.Tok, IDomCall, Call); } else { Asm4(n.Tok, IDomGet, Dst, n.Reg, Name, Arr); } n.Reg = Dst; } } else if (n.IsConst()) { // Constant switch (n.ConstTok) { case TTrue: { AllocConst(n.Reg, true); break; } case TFalse: { AllocConst(n.Reg, false); break; } case TNull: { AllocNull(n.Reg); break; } default: { if (n.Lst.Length()) { // List/array constant LVariant *v = PreAllocVariant(n.Reg); if (v) { v->SetList(); for (unsigned i=0; i a(new LVariant); if (!ConvertStringToVariant(a, t)) break; v->Value.Lst->Insert(a.Release()); } } } else { char16 *t = Tokens[n.Tok]; if (*t == '\"' || *t == '\'') { // string ssize_t Len = StrlenW(t); AllocConst(n.Reg, t + 1, Len - 2); } else if (StrchrW(t, '.')) { // double AllocConst(n.Reg, atof(t)); } else if (t[0] == '0' && tolower(t[1]) == 'x') { // hex integer AllocConst(n.Reg, htoi(t + 2)); } else { // decimal integer AllocConst(n.Reg, Atoi(t)); } } break; } } LValue = NULL; } else if (n.IsContextFunc()) { // Method call, create byte codes to put func value into n.Reg LVarRef *OutRef; if (LValue) { OutRef = LValue; } else { if (!AllocReg(n.Reg, n.Tok, _FL)) { return OnError(n.Tok, "Can't allocate register for method return value."); } OutRef = &n.Reg; } DebugInfo(n.Tok); LArray a; for (unsigned i=0; iByteCode.Length(); ssize_t Size = 1 + sizeof(LFunc*) + sizeof(LVarRef) + 2 + (a.Length() * sizeof(LVarRef)); Code->ByteCode.Length(Len + Size); LScriptPtr p; uint8_t *Start = &Code->ByteCode[Len]; p.u8 = Start; *p.u8++ = ICallMethod; *p.fn++ = n.ContextFunc; n.ContextFunc->InUse = true; *p.r++ = *OutRef; *p.u16++ = (uint16) n.Args.Length(); for (unsigned i=0; iGetName(); if (*FnName == 'D' && !_stricmp(FnName, sDebugger)) { Asm0(n.Tok, IDebug); return true; } if (n.ScriptFunc->GetParams().Length() != n.Args.Length()) { if (n.ScriptFunc->ValidStartAddr()) { // Function is already defined and doesn't have the right arg count... return OnError( n.Tok, "Wrong number of parameters: %i (%s expects %i)", n.Args.Length(), FnName, n.ScriptFunc->GetParams().Length()); } else { // Maybe it's defined later or in a separate file? // Or maybe it's just not defined at all? // Can't know until later... flag it as a unresolved external... // // Allow the assembly for the call to occur. } } // Call to a script function, create byte code to call function LVarRef *OutRef; if (LValue) { OutRef = LValue; } else { if (!AllocReg(n.Reg, n.Tok, _FL)) { return OnError(n.Tok, "Can't allocate register for method return value."); } OutRef = &n.Reg; } DebugInfo(n.Tok); LArray a; for (unsigned i=0; iByteCode.Length(); size_t Size = 1 + // instruction sizeof(uint32_t) + // address of function sizeof(uint16) + // size of frame sizeof(LVarRef) + // return value 2 + // number of args (a.Length() * sizeof(LVarRef)); // args Code->ByteCode.Length(Len + Size); LScriptPtr p; uint8_t *Start = &Code->ByteCode[Len]; p.u8 = Start; *p.u8++ = ICallScript; if (n.ScriptFunc->ValidStartAddr() && n.ScriptFunc->ValidFrameSize()) { // Compile func address straight into code... *p.u32++ = n.ScriptFunc->GetStartAddr(); *p.u16++ = n.ScriptFunc->GetFrameSize(); } else { // Add link time fix up LinkFixup &Fix = Fixups.New(); Fix.Tok = n.Tok; Fix.Args = (int)n.Args.Length(); Fix.Offset = p.u8 - &Code->ByteCode[0]; Fix.Func = n.ScriptFunc; *p.u32++ = 0; *p.u16++ = 0; } *p.r++ = *OutRef; *p.u16++ = (uint16) n.Args.Length(); for (unsigned i=0; i e(new Node::NodeExp); if (!e) return OnError(Cur, "Mem alloc error."); if (!Expression(Cur, *e)) return OnError(Cur, "Couldn't parse func call argument expression."); vp.Args.Add(e.Release()); t = GetTok(Cur); if (!t) return OnError(Cur, "Unexpected end of file."); if (!StricmpW(t, sComma)) Cur++; else if (!StricmpW(t, sEndRdBracket)) break; else return OnError(Cur, "Expecting ',', didn't get it."); } } t = GetTok(Cur+1); } // Check for DOM operator... if (StricmpW(t, sPeriod) == 0) { // Got Dom operator Cur += 2; t = GetTok(Cur); if (!t) return OnError(Cur, "Unexpected eof."); Var.Variable.New().Name = t; } else break; } return true; } /// Parse expression into a node tree bool Expression(uint32_t &Cur, LArray &n, int Depth = 0) { if (Cur >= Tokens.Length()) return OnError(Cur, "Unexpected end of file."); char16 *t; bool PrevIsOp = true; while ((t = Tokens[Cur])) { LTokenType Tok = ExpTok.Find(t); if (Tok == TTypeId) { char16 *v; if (!DoTypeId(Cur, v)) return false; Node &Var = n.New(); t = v; Cur--; DoVariableNode(Cur, Var, t); Var.ConstTok = TTypeId; Cur++; } else if (Tok == TStartRdBracket) { Cur++; auto &Next = n.New(); if (!Expression(Cur, Next.Child, Depth + 1)) return false; PrevIsOp = false; if (Next.Child.Length() > 0) Next.Tok = Next.Child[0].Tok; } else if (Tok == TEndRdBracket) { if (Depth > 0) Cur++; break; } else if (Tok == TComma || Tok == TSemiColon) { break; } else if (Depth == 0 && Tok == TEndSqBracket) { break; } else if (Tok == TTrue || Tok == TFalse || Tok == TNull) { n.New().SetConst(Cur++, Tok); } else { LOperator o = IsOp(t, PrevIsOp); if (o != OpNull) { // Operator PrevIsOp = 1; n.New().SetOp(o, Cur); } else { PrevIsOp = 0; LVariant m; m = t; LFunc *f = Methods.Find(m.Str()); LFunctionInfo *sf = 0; char16 *Next = GetTok(Cur+1); bool IsFunctionCall = !StricmpW(Next, sStartRdBracket); if (f && IsFunctionCall) { Node &Call = n.New(); Call.SetContextFunction(f, Cur++); // Now parse arguments // Get the start bracket if ((t = GetTok(Cur))) { if (StricmpW(t, sStartRdBracket) == 0) Cur++; else return OnError(Cur, "Function missing '('"); } else return OnError(Cur, "No token."); // Parse the args as expressions while ((t = GetTok(Cur))) { LTokenType Tok = ExpTok.Find(t); if (Tok == TComma) { // Do nothing... Cur++; } else if (Tok == TEndRdBracket) { break; } else if (Tok == TSemiColon) { return OnError(Cur, "Unexpected ';'"); } else if (!Expression(Cur, Call.Args.New())) { return OnError(Cur, "Can't parse function argument."); } } } else if ( (sf = Code->GetMethod(m.Str(), IsFunctionCall)) ) { Node &Call = n.New(); Call.SetScriptFunction(sf, Cur++); // Now parse arguments // Get the start bracket if ((t = GetTok(Cur))) { if (StricmpW(t, sStartRdBracket) == 0) Cur++; else return OnError(Cur, "Function missing '('"); } else return OnError(Cur, "No token."); // Parse the args as expressions while ((t = GetTok(Cur))) { if (StricmpW(t, sComma) == 0) { // Do nothing... Cur++; } else if (StricmpW(t, sEndRdBracket) == 0) { break; } else if (!Expression(Cur, Call.Args[Call.Args.Length()])) { return OnError(Cur, "Can't parse function argument."); } } } else if (IsAlpha(*t)) { // Variable... Node &Var = n.New(); if (!DoVariableNode(Cur, Var, t)) return false; } else if (*t == '\'' || *t == '\"' || LIsNumber(t)) { // Constant string or number n.New().SetConst(Cur, TLiteral); } else if (Tok == TStartCurlyBracket) { // List definition LArray Values; Cur++; int Index = 0; while ((t = Tokens[Cur])) { LTokenType Tok = ExpTok.Find(t); if (Tok == TComma) { Index++; } else if (Tok == TEndCurlyBracket) { break; } else if (*t == '\'' || *t == '\"' || LIsNumber(t)) { Values[Index] = Cur; } else { return OnError(Cur, "Unexpected token '%S' in list definition", t); } Cur++; } n.New().SetConst(Values, TLiteral); } else { // Unknown return OnError(Cur, "Unknown token '%S'", t); } } Cur++; } } return true; } /// Allocate a register (must be mirrored with DeallocReg) bool AllocReg(LVarRef &r, int LineOrTok, const char *file, int line) { for (int i=0; iPrint("CompileError:Register[%i] allocated by %s\n", n, a); } } #endif return false; } /// Deallocate a register bool DeallocReg(LVarRef &r) { if (r.Scope == SCOPE_REGISTER && r.Index >= 0) { int Bit = 1 << r.Index; // LAssert((Bit & Regs) != 0); Regs &= ~Bit; #ifdef _DEBUG RegAllocators[r.Index].Empty(); #endif } return true; } /// Count allocated registers int RegAllocCount() { int c = 0; for (int i=0; i &n) { LStringPipe e; for (unsigned i=0; iMethod.Get()); } else if (n[i].ScriptFunc) { e.Print("%s(...)", n[i].ScriptFunc->GetName()); } else { e.Print("#err#"); } } return e.NewStr(); } /// Creates byte code to evaluate an expression bool AsmExpression ( /// Where the result got stored LVarRef *Result, /// The nodes to create code for LArray &n, /// The depth of recursion int Depth = 0 ) { // Resolve any sub-expressions and store their values for (unsigned i = 0; i < n.Length(); i++) { auto &k = n[i]; if (!k.IsVar() && k.Child.Length()) { AllocReg(k.Reg, k.Tok, _FL); AsmExpression(&k.Reg, k.Child, Depth + 1); } } while (n.Length() > 1) { // Find which operator to handle first #ifdef _DEBUG size_t StartLength = n.Length(); #endif int OpIdx = -1; int Prec = -1; int Ops = 0; for (unsigned i=0; i Jmp; if (!TokenToVarRef(a, NullRef)) return OnError(a.Tok, "Can't convert left token to var ref."); if (Op == OpAnd) { // Jump over 'b' if 'a' is FALSE Jmp.Reset(new LJumpZero(this, a.Tok, a.Reg, false)); } else if (Op == OpOr) { // Jump over 'b' if 'a' is TRUE } if (!TokenToVarRef(b, LValue)) return OnError(b.Tok, "Can't convert right token to var ref."); LVarRef Reg; Reg.Empty(); if (a.Reg.Scope != SCOPE_REGISTER) { if (AllocReg(Reg, a.Tok, _FL)) { LAssert(Reg != a.Reg); Asm2(a.Tok, IAssign, Reg, a.Reg); a.Reg = Reg; } else return OnError(a.Tok, "Can't alloc register, Regs=0x%x", Regs); } Asm2(a.Tok, Op, a.Reg, b.Reg); if (Op == OpPlusEquals || Op == OpMinusEquals || Op == OpMulEquals || Op == OpDivEquals) { AssignVarRef(a, a.Reg); } } if (a.Reg != b.Reg) DeallocReg(b.Reg); n.DeleteAt(OpIdx+1, true); n.DeleteAt(OpIdx, true); // Result is in n[OpIdx-1] (ie the 'a' node) } else { LAssert(!"Not a valid type"); return OnError(n[0].Tok, "Not a valid type."); } #ifdef _DEBUG if (StartLength == n.Length()) { // No nodes removed... infinite loop! LAssert(!"No nodes removed."); return false; } #endif } if (n.Length() == 1) { auto &k = n[0]; if (!k.Reg.Valid()) { LVarRef *NullRef = NULL; if (!TokenToVarRef(k, NullRef)) { return false; } } if (Result) { if (Result->Valid() && *Result != k.Reg) DeallocReg(*Result); *Result = k.Reg; } else { DeallocReg(k.Reg); } return true; } return false; } /// Parses and assembles an expression bool DoExpression(uint32_t &Cur, LVarRef *Result) { LArray n; if (Expression(Cur, n)) { bool Status = AsmExpression(Result, n); return Status; } return false; } /// Converts a variable to it's type information bool DoTypeId(uint32_t &Cur, char16 *&Var) { if (GetTokType(Cur) != TTypeId) return OnError(Cur, "Expecting 'typeid'."); Cur++; if (GetTokType(Cur) != TStartRdBracket) return OnError(Cur, "Expecting '('."); Cur++; char16 *t = GetTok(Cur); if (!t || !IsAlpha(*t)) return OnError(Cur, "Expecting variable name."); Cur++; if (GetTokType(Cur) != TEndRdBracket) return OnError(Cur, "Expecting ')'."); Cur++; Var = t; return true; } /// Parses statements bool DoStatements(uint32_t &Cur, bool *LastWasReturn, bool MoreThanOne = true) { while (Cur < Tokens.Length()) { char16 *t = GetTok(Cur); if (!t) break; LTokenType Tok = ExpTok.Find(t); switch (Tok) { case TTypeId: { return OnError(Cur, "typeif only valid in an expression."); } case TSemiColon: { Cur++; break; } case TDebug: { Asm0(Cur++, IDebug); break; } case TBreakPoint: { Asm0(Cur++, IBreakPoint); break; } case TReturn: { Cur++; if (!DoReturn(Cur)) return false; if (LastWasReturn) *LastWasReturn = true; break; } case TEndCurlyBracket: case TFunction: { return true; } case TIf: { if (!DoIf(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } case TFor: { if (!DoFor(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } case TWhile: { if (!DoWhile(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } case TExtern: { if (!DoExtern(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } default: { if (!DoExpression(Cur, 0)) return false; LTokenType Tok = ExpTok.Find(GetTok(Cur)); if (Tok == TSemiColon) Cur++; if (LastWasReturn) *LastWasReturn = false; break; } } if (!MoreThanOne) break; } return true; } /// Parses if/else if/else construct bool DoIf(uint32_t &Cur) { Cur++; char16 *t; if (GetTokType(Cur) == TStartRdBracket) { Cur++; // Compile and asm code to evaluate the expression LVarRef Result; int ExpressionTok = Cur; Result.Empty(); if (DoExpression(Cur, &Result)) { t = GetTok(Cur); if (!t || StricmpW(t, sEndRdBracket)) return OnError(Cur, "if missing ')'."); Cur++; t = GetTok(Cur); if (!t) return OnError(Cur, "if missing body statement."); // Output the jump instruction LAutoPtr Jmp(new LJumpZero(this, ExpressionTok, Result)); if (!StricmpW(t, sStartCurlyBracket)) { // Statement block Cur++; while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } else { // Single statement if (!DoStatements(Cur, NULL, false)) return false; } // Check for else... if ((t = GetTok(Cur)) && StricmpW(t, sElse) == 0) { // Add a jump for the "true" part of the expression to // jump over the "else" part. Asm0(Cur, IJump); size_t JOffset = Code->ByteCode.Length(); if (Code->ByteCode.Length(JOffset + 4)) { // Initialize the ptr to zero int32 *Ptr = (int32*)&Code->ByteCode[JOffset]; *Ptr = 0; // Resolve jz to here... Jmp.Reset(); // Compile the else block Cur++; if ((t = GetTok(Cur)) && StricmpW(t, sStartCurlyBracket) == 0) { // 'Else' Statement block Cur++; while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } else { // Single statement if (!DoStatements(Cur, NULL, false)) return false; } // Resolve the "JOffset" jump that takes execution of // the 'true' part over the 'else' part Ptr = (int32*)&Code->ByteCode[JOffset]; *Ptr = (int32) (Code->ByteCode.Length() - JOffset - sizeof(int32)); if (*Ptr == 0) { // Empty statement... so delete the Jump instruction Code->ByteCode.Length(JOffset - 1); } } else OnError(Cur, "Mem alloc"); } return true; } } else return OnError(Cur, "if missing '('"); return false; } LArray &GetByteCode() { return Code->ByteCode; } class LJumpZero { LCompilerPriv *Comp; int JzOffset; public: LJumpZero(LCompilerPriv *d, int Tok, LVarRef &r, bool DeallocReg = true) { // Create jump instruction to jump over the body if the expression evaluates to false Comp = d; Comp->Asm1(Tok, IJumpZero, r); if (DeallocReg) Comp->DeallocReg(r); JzOffset = (int) Comp->GetByteCode().Length(); Comp->GetByteCode().Length(JzOffset + sizeof(int32)); } ~LJumpZero() { // Resolve jump int32 *Ptr = (int32*) &Comp->GetByteCode()[JzOffset]; *Ptr = (int32) (Comp->GetByteCode().Length() - (JzOffset + sizeof(int32))); } }; /// Parses while construct bool DoWhile(uint32_t &Cur) { Cur++; char16 *t = GetTok(Cur); if (!t || StricmpW(t, sStartRdBracket)) return OnError(Cur, "Expecting '(' after 'while'"); Cur++; // Store start of condition code size_t ConditionStart = Code->ByteCode.Length(); // Compile condition evalulation LVarRef r; r.Empty(); if (!DoExpression(Cur, &r)) return false; // Create jump instruction to jump over the body if the expression evaluates to false { LJumpZero Jump(this, Cur, r); if (!(t = GetTok(Cur)) || StricmpW(t, sEndRdBracket)) return OnError(Cur, "Expecting ')'"); Cur++; // Compile the body of the loop if (!(t = GetTok(Cur))) return OnError(Cur, "Unexpected eof."); Cur++; if (StricmpW(t, sStartCurlyBracket) == 0) { // Block while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } else { // Single statement DoStatements(Cur, NULL, false); } // Jump to condition evaluation at 'ConditionStart' Asm0(Cur, IJump); size_t JOffset = Code->ByteCode.Length(); Code->ByteCode.Length(JOffset + sizeof(int32)); int32 *Ptr = (int32*) &Code->ByteCode[JOffset]; *Ptr = (int32) (ConditionStart - Code->ByteCode.Length()); } return true; } /// Parses for construct bool DoFor(uint32_t &Cur) { /* For loop asm structure: +---------------------------+ | Pre-condition expression | +---------------------------+ | Eval loop contdition exp. |<--+ | | | | JUMP ZERO (jumpz) |---+-+ +---------------------------+ | | | Body of loop... | | | | | | | | | | | | | | | | | | | | | | | +---------------------------+ | | | Post-cond. expression | | | | | | | | JUMP |---+ | +---------------------------+ | | Following code... |<----+ . . */ Cur++; char16 *t = GetTok(Cur); if (!t || StricmpW(t, sStartRdBracket)) return OnError(Cur, "Expecting '(' after 'for'"); Cur++; // Compile initial statement LVarRef r; r.Empty(); t = GetTok(Cur); if (!t) return false; if (StricmpW(t, sSemiColon) && !DoExpression(Cur, 0)) return false; t = GetTok(Cur); // Look for ';' if (!t || StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';'"); Cur++; // Store start of condition code size_t ConditionStart = Code->ByteCode.Length(); // Compile condition evalulation if (!DoExpression(Cur, &r)) return false; { LJumpZero Jmp(this, Cur, r); t = GetTok(Cur); // Look for ';' if (!t || StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';'"); Cur++; // Compile the post expression code size_t PostCodeStart = Code->ByteCode.Length(); t = GetTok(Cur); if (StricmpW(t, sEndRdBracket) && !DoExpression(Cur, 0)) return false; // Store post expression code in temp variable LArray PostCode; size_t PostCodeLen = Code->ByteCode.Length() - PostCodeStart; if (PostCodeLen) { PostCode.Length(PostCodeLen); memcpy(&PostCode[0], &Code->ByteCode[PostCodeStart], PostCodeLen); // Delete the post expression off the byte code, we are putting it after the block code Code->ByteCode.Length(PostCodeStart); } // Look for ')' t = GetTok(Cur); if (!t || StricmpW(t, sEndRdBracket)) return OnError(Cur, "Expecting ')'"); Cur++; // Compile body of loop if ((t = GetTok(Cur)) && StricmpW(t, sStartCurlyBracket) == 0) { Cur++; while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } // Add post expression code if (PostCodeLen) { size_t Len = Code->ByteCode.Length(); Code->ByteCode.Length(Len + PostCodeLen); memcpy(&Code->ByteCode[Len], &PostCode[0], PostCodeLen); } // Jump to condition evaluation at 'ConditionStart' Asm0(Cur, IJump); size_t JOffset = Code->ByteCode.Length(); Code->ByteCode.Length(JOffset + sizeof(int32)); int32 *Ptr = (int32*) &Code->ByteCode[JOffset]; *Ptr = (int32) (ConditionStart - Code->ByteCode.Length()); } return true; } /// Compiles return construct bool DoReturn(uint32_t &Cur) { LVarRef ReturnValue; char16 *t; int StartTok = Cur; ReturnValue.Empty(); LArray Exp; if (!Expression(Cur, Exp)) { return OnError(Cur, "Failed to compile return expression."); } else if (!AsmExpression(&ReturnValue, Exp)) { return OnError(Cur, "Failed to assemble return expression."); } if (!(t = GetTok(Cur)) || StricmpW(t, sSemiColon)) { return OnError(Cur, "Expecting ';' after return expression."); } Asm1(StartTok, IRet, ReturnValue); DeallocReg(ReturnValue); Cur++; return true; } // Compile a method definition bool DoFunction ( /// Cursor token index uint32_t &Cur, /// [Optional] Current struct / class. /// If not NULL this is a method definition for said class. LCustomType *Struct = NULL ) { bool Status = false; bool LastInstIsReturn = false; if (!JumpLoc) { size_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 5)) { LScriptPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = IJump; *p.i32++ = 0; JumpLoc = Len + 1; } else OnError(Cur, "Mem alloc failed."); } LString FunctionName; LCustomType::Method *StructMethod = NULL; // Member function of script struct/class LFunctionInfo *ScriptMethod = NULL; // Standalone scripting function char16 *Name = GetTok(Cur); if (Name) { Cur++; char16 *t = GetTok(Cur); if (!t || StricmpW(t, sStartRdBracket)) return OnError(Cur, "Expecting '(' in function."); FunctionName = Name; // Parse parameters LArray Params; Cur++; while ((t = GetTok(Cur))) { if (IsAlpha(*t)) { Params.New() = t; Cur++; if (!(t = GetTok(Cur))) goto UnexpectedFuncEof; } if (!StricmpW(t, sComma)) ; else if (!StricmpW(t, sEndRdBracket)) { Cur++; break; } else goto UnexpectedFuncEof; Cur++; } if (Struct) { StructMethod = Struct->DefineMethod(FunctionName, Params, Code->ByteCode.Length()); } else { ScriptMethod = Code->GetMethod(FunctionName, true); if (!ScriptMethod) return OnError(Cur, "Can't define method '%s'.", FunctionName.Get()); ScriptMethod->GetParams() = Params; ScriptMethod->SetStartAddr((int32_t)Code->ByteCode.Length()); } // Parse start of body if (!(t = GetTok(Cur))) goto UnexpectedFuncEof; if (StricmpW(t, sStartCurlyBracket)) return OnError(Cur, "Expecting '{'."); // Setup new scope(s) LAutoPtr ObjectScope; if (Struct) { // The object scope has to be first so that local variables take // precedence over object member variables. if (ObjectScope.Reset(new LVariables(Struct))) Scopes.Add(ObjectScope); } LVariables LocalScope(SCOPE_LOCAL); if (Struct) LocalScope.Var("This", true); for (unsigned i=0; iFrameSize = (uint16)LocalScope.Length(); else if (ScriptMethod) ScriptMethod->SetFrameSize(LocalScope.Length()); else LAssert(!"What are you defining exactly?"); Status = true; Cur++; // LgiTrace("Added method %s @ %i, stack=%i, args=%i\n", f->Name.Str(), f->StartAddr, f->FrameSize, f->Params.Length()); break; } // Parse statement in the body if (!DoStatements(Cur, &LastInstIsReturn)) return OnError(Cur, "Can't compile function body."); } // Remove any scopes we created Scopes.PopLast(); if (Struct) Scopes.PopLast(); } if (!LastInstIsReturn) { LVarRef RetVal; AllocNull(RetVal); Asm1(Cur, IRet, RetVal); } return Status; UnexpectedFuncEof: return OnError(Cur, "Unexpected EOF in function."); } LExternFunc::ExternType ParseExternType(LArray &Strs) { LExternFunc::ExternType Type; Type.Out = false; Type.Ptr = 0; Type.ArrayLen = 1; Type.Base = GV_NULL; Type.Unsigned = false; bool InArray = false; for (unsigned i=0; i Tok; const char16 *t; while ((t = GetTok(Cur))) { if (!StricmpW(t, sStartRdBracket)) { Cur++; break; } else Tok.Add(t); Cur++; } if (Tok.Length() < 3) { return OnError(Cur, "Not enough tokens in extern decl."); } // First token is library name LExternFunc *e = new LExternFunc; if (!e) return OnError(Cur, "Alloc error."); Code->Externs.Add(e); e->Type = ExternFunc; char16 sQuotes[] = {'\'','\"',0}; LAutoWString LibName(TrimStrW(Tok[0], sQuotes)); e->Lib.Reset(WideToUtf8(LibName)); Tok.DeleteAt(0, true); e->Method = Tok.Last(); Tok.DeleteAt(Tok.Length()-1, true); if (!ValidStr(e->Method)) { Code->Externs.Length(Code->Externs.Length()-1); return OnError(Cur, "Failed to get extern method name."); } // Parse return type e->ReturnType = ParseExternType(Tok); // Parse argument types Tok.Length(0); while ((t = GetTok(Cur))) { if (!StricmpW(t, sEndRdBracket)) { e->ArgType.New() = ParseExternType(Tok); Cur++; break; } else if (!StricmpW(t, sComma)) { e->ArgType.New() = ParseExternType(Tok); } else Tok.Add(t); Cur++; } if ((t = GetTok(Cur))) { if (StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';' in extern decl."); Cur++; } Methods.Add(e->Method, e); return true; } struct ExpPart { LOperator Op; int Value; ExpPart() { Op = OpNull; Value = 0; } }; int Evaluate(LArray &Exp, size_t Start, size_t End) { LArray p; // Find outer brackets ssize_t e; for (e = End; e >= (ssize_t)Start; e--) { if (Exp[e][0] == ')') break; } for (size_t i = Start; i <= End; i++) { char16 *t = Exp[i]; if (*t == '(') { p.New().Value = Evaluate(Exp, i + 1, e - 1); i = e; } else { LOperator op = IsOp(t, 0); if (op) { p.New().Op = op; } else if (IsDigit(*t)) { p.New().Value = AtoiW(t); } else { LAssert(0); break; } } } while (p.Length() > 1) { int HighPrec = 32; int Idx = -1; for (unsigned i=0; i 0 && Idx < (int)p.Length() - 1) { switch (p[Idx].Op) { case OpPlus: p[Idx-1].Value += p[Idx+1].Value; break; case OpMinus: p[Idx-1].Value -= p[Idx+1].Value; break; case OpMul: p[Idx-1].Value *= p[Idx+1].Value; break; case OpDiv: if (p[Idx+1].Value) p[Idx-1].Value /= p[Idx+1].Value; else LAssert(!"Div 0"); break; default: LAssert(!"Impl me."); break; } p.DeleteAt(Idx, true); p.DeleteAt(Idx, true); } else { LAssert(0); break; } } else { LAssert(!"Impl me."); } } if (p.Length() == 1) { LAssert(p[0].Op == OpNull); return p[0].Value; } LAssert(0); return 0; } int ByteSizeFromType(char *s, LVariantType t) { switch (t) { case GV_INT32: { char n[16], *o = n; for (char *c = s; *c; c++) { if (IsDigit(*c) && o < n + sizeof(n) - 1) *o++ = *c; } *o++ = 0; int bits = ::atoi(n); switch (bits) { case 8: return 1; case 16: return 2; } return 4; break; } case GV_INT64: { return 8; } case GV_WSTRING: { return sizeof(char16); } default: break; } return 1; } /// Compiles struct construct bool DoStruct(uint32_t &Cur) { bool Status = false; // Parse struct name and setup a type char16 *t; LCustomType *Def = Code->Types.Find(t = GetTok(Cur)); if (!Def) Code->Types.Add(t, Def = new LCustomType(t)); Cur++; t = GetTok(Cur); if (!t || StricmpW(t, sStartCurlyBracket)) return OnError(Cur, "Expecting '{'"); Cur++; // Parse members while ((t = GetTok(Cur))) { // End of type def? LTokenType tt = ExpTok.Find(t); if (tt == TEndCurlyBracket) { Cur++; tt = GetTokType(Cur); if (!tt || tt != TSemiColon) return OnError(Cur, "Expecting ';' after '}'"); Status = true; break; } if (tt == TFunction) { Cur++; if (!DoFunction(Cur, Def)) return false; } else { // Parse member field LVariant TypeName = t; LCustomType *NestedType = 0; LVariantType Type = Types.Find(TypeName.Str()); if (!Type) { // Check other custom types NestedType = Code->GetType(t); if (!NestedType) return OnError(Cur, "Unknown type '%S' in struct definition.", t); // Ok, nested type. Type = GV_CUSTOM; } Cur++; if (!(t = GetTok(Cur))) goto EofError; bool Pointer = false; if (t[0] == '*' && t[1] == 0) { Pointer = true; Cur++; if (!(t = GetTok(Cur))) goto EofError; } LVariant Name = t; Cur++; if (!(t = GetTok(Cur))) goto EofError; int Array = 1; if (!StricmpW(t, sStartSqBracket)) { // Array Cur++; LArray Exp; while ((t = GetTok(Cur))) { Cur++; if (!StricmpW(t, sEndSqBracket)) break; Exp.Add(t); } Array = Evaluate(Exp, 0, Exp.Length()-1); } int MemberAddr = Def->AddressOf(Name.Str()); if (MemberAddr >= 0) return OnError(Cur, "Member '%s' can't be defined twice.", Name.Str()); if (NestedType) { if (!Def->DefineField(Name.Str(), NestedType, Array)) return OnError(Cur, "Failed to define field '%s'.", Name.Str()); } else { int Bytes = ByteSizeFromType(TypeName.Str(), Type); if (!Def->DefineField(Name.Str(), Type, Bytes, Array)) return OnError(Cur, "Failed to define field '%s'.", Name.Str()); } t = GetTok(Cur); if (StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';'"); Cur++; } } return Status; EofError: return OnError(Cur, "Unexpected EOF."); } /// Compiler entry point bool Compile() { uint32_t Cur = 0; JumpLoc = 0; // Setup the global scope Scopes.Length(0); Scopes.Add(&Code->Globals); // Compile the code... while (Cur < Tokens.Length()) { char16 *t = GetTok(Cur); if (!t) break; if (*t == '#' || StricmpW(t, sSemiColon) == 0) { Cur++; } else if (!StricmpW(t, sFunction)) { if (!DoFunction(++Cur)) return false; } else if (!StricmpW(t, sStruct)) { if (!DoStruct(++Cur)) return false; } else if (!StricmpW(t, sEndCurlyBracket)) { return OnError(Cur, "Not expecting '}'."); } else if (!StricmpW(t, sExtern)) { if (!DoExtern(++Cur)) return false; } else { if (JumpLoc) { LScriptPtr p; p.u8 = &Code->ByteCode[JumpLoc]; *p.u32 = (uint32_t) (Code->ByteCode.Length() - (JumpLoc + 4)); JumpLoc = 0; } if (!DoStatements(Cur, NULL)) { return OnError(Cur, "Statement compilation failed."); } } } if (JumpLoc) { LScriptPtr p; p.u8 = &Code->ByteCode[JumpLoc]; *p.u32 = (uint32_t) (Code->ByteCode.Length() - (JumpLoc + 4)); JumpLoc = 0; } // Do link time fix ups... for (unsigned i=0; iValidStartAddr()) { if (f.Args != f.Func->GetParams().Length()) { return OnError(f.Tok, "Function call '%s' has wrong arg count (caller=%i, method=%i).", f.Func->GetName(), f.Args, f.Func->GetParams().Length()); } else if (!f.Func->ValidFrameSize()) { return OnError(f.Tok, "Function call '%s' has no frame size.", f.Func->GetName()); } else { LScriptPtr p; p.u8 = &Code->ByteCode[f.Offset]; LAssert(*p.u32 == 0); *p.u32++ = f.Func->GetStartAddr(); *p.u16++ = f.Func->GetFrameSize(); } } else { return OnError(f.Tok, "Function '%s' not defined.", f.Func->GetName()); } } Fixups.Length(0); return true; } }; LCompiler::LCompiler() { d = new LCompilerPriv; } LCompiler::~LCompiler() { DeleteObj(d); } bool LCompiler::Compile ( LAutoPtr &Code, LScriptContext *SysContext, LScriptContext *UserContext, const char *FileName, const char *Script, LDom *Args ) { if (!Script) return false; LStringPipe p; #ifdef DEBUG_SCRIPT_FILE d->Debug = Stristr(FileName, DEBUG_SCRIPT_FILE) != NULL; #endif if (SysContext && SysContext->GetLog()) d->Log = SysContext->GetLog(); else if (UserContext && UserContext->GetLog()) d->Log = UserContext->GetLog(); else d->Log = &p; d->Methods.Empty(); if (SysContext) { LHostFunc *f = SysContext->GetCommands(); for (int i=0; f[i].Method; i++) { f[i].Context = SysContext; d->Methods.Add(f[i].Method, &f[i]); } } d->SysCtx = SysContext; d->UserCtx = UserContext; if (d->UserCtx) { LHostFunc *f = d->UserCtx->GetCommands(); - for (int i=0; f[i].Method; i++) + if (f) { - f[i].Context = d->UserCtx; - - if (!d->Methods.Find(f[i].Method)) - d->Methods.Add(f[i].Method, f+i); - else + for (int i=0; f[i].Method; i++) { - 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."); + f[i].Context = d->UserCtx; + + if (!d->Methods.Find(f[i].Method)) + d->Methods.Add(f[i].Method, f+i); + else + { + LgiTrace("%s:%i - Conflicting name of method in application's context: '%s'\n", _FL, f[i].Method.Get()); + LAssert(!"Conflicting name of method in application's context."); + } } } } if (!Code) Code.Reset(new LCompiledCode); bool Status = false; d->Code = dynamic_cast(Code.Get()); if (d->Code) { d->Code->UserContext = UserContext; d->Code->SysContext = SysContext; d->Code->SetSource(FileName, Script); bool LexResult = d->Lex((char*)Script, FileName); if (LexResult) { d->ScriptArgs = Args; Status = d->Compile(); } else { d->OnError(0, "Failed to lex script.\n"); } } else { d->OnError(0, "Allocation failed.\n"); } d->Code = NULL; return Status; } ////////////////////////////////////////////////////////////////////// class LScriptEnginePrivate { public: LViewI *Parent; SystemFunctions SysContext; LScriptContext *UserContext; LCompiledCode *Code; LVmCallback *Callback; LVariant ReturnValue; LScriptEnginePrivate() { UserContext = NULL; Parent = NULL; Code = NULL; Callback = NULL; } }; LScriptEngine::LScriptEngine(LViewI *parent, LScriptContext *UserContext, LVmCallback *Callback) { d = new LScriptEnginePrivate; d->Parent = parent; d->UserContext = UserContext; d->Callback = Callback; d->SysContext.SetEngine(this); } LScriptEngine::~LScriptEngine() { DeleteObj(d); } LCompiledCode *LScriptEngine::GetCurrentCode() { return d->Code; } bool LScriptEngine::Compile(LAutoPtr &Obj, LScriptContext *UserContext, const char *Script, const char *FileName, LDom *Args) { if (!Script) { LAssert(!"Param error"); return NULL; } LCompiler Comp; return Comp.Compile(Obj, &d->SysContext, UserContext ? UserContext : d->UserContext, FileName, Script, Args); } LExecutionStatus LScriptEngine::Run(LCompiledCode *Obj, LVariant *Ret, const char *TempPath) { LExecutionStatus Status = ScriptError; d->Code = Obj; if (d->Code) { LVirtualMachine Vm(d->Callback); if (TempPath) Vm.SetTempPath(TempPath); Status = Vm.Execute(d->Code, 0, NULL, true, Ret ? Ret : &d->ReturnValue); d->Code = NULL; } return Status; } LExecutionStatus LScriptEngine::RunTemporary(LCompiledCode *Obj, char *Script, LVariant *Ret) { LExecutionStatus Status = ScriptError; LCompiledCode *Code = dynamic_cast(Obj); if (Script && Code) { LAutoPtr Temp(new LCompiledCode(*Code)); uint32_t TempLen = (uint32_t) Temp->Length(); d->Code = Temp; LCompiler Comp; if (Comp.Compile(Temp, &d->SysContext, d->UserContext, Temp->GetFileName(), Script, NULL)) { LVirtualMachine Vm(d->Callback); Status = Vm.Execute(dynamic_cast(Temp.Get()), TempLen, NULL, true, Ret ? Ret : &d->ReturnValue); } d->Code = NULL; } return Status; } bool LScriptEngine::EvaluateExpression(LVariant *Result, LDom *VariableSource, const char *Expression) { if (!Result || !VariableSource || !Expression) { LAssert(!"Param error"); return false; } // Create trivial script to evaluate the expression LString a; a.Printf("return %s;", Expression); // Compile the script LCompiler Comp; LAutoPtr Obj; if (!Comp.Compile(Obj, &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/linux/Lgi/App.cpp b/src/linux/Lgi/App.cpp --- a/src/linux/Lgi/App.cpp +++ b/src/linux/Lgi/App.cpp @@ -1,1477 +1,1477 @@ #include #include #include #include #ifndef WIN32 #include #include #endif #include #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/Array.h" #include "lgi/common/Variant.h" #include "lgi/common/Token.h" #include "lgi/common/FontCache.h" #include "AppPriv.h" #define DEBUG_MSG_TYPES 0 #define DEBUG_HND_WARNINGS 0 #define IDLE_ALWAYS 0 using namespace Gtk; bool GlibWidgetSearch(GtkWidget *p, GtkWidget *w, bool Debug, int depth = 0); //////////////////////////////////////////////////////////////// struct OsAppArgumentsPriv { LArray Ptr; ~OsAppArgumentsPriv() { Ptr.DeleteArrays(); } }; OsAppArguments::OsAppArguments(int args, const char **arg) { d = new OsAppArgumentsPriv; for (int i=0; iPtr.Add(NewStr(arg[i])); } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; } OsAppArguments::~OsAppArguments() { DeleteObj(d); } bool OsAppArguments::Get(const char *Name, const char **Val) { if (!Name) return false; for (int i=0; iPtr.DeleteArrays(); if (!CmdLine) return; for (char *s = CmdLine; *s; ) { while (*s && strchr(WhiteSpace, *s)) s++; if (*s == '\'' || *s == '\"') { char delim = *s++; char *e = strchr(s, delim); if (e) d->Ptr.Add(NewStr(s, e - s)); else break; s = e + 1; } else { char *e = s; while (*e && !strchr(WhiteSpace, *e)) e++; d->Ptr.Add(NewStr(s, e-s)); s = e; } } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; } OsAppArguments &OsAppArguments::operator =(OsAppArguments &a) { d->Ptr.DeleteArrays(); for (int i=0; iPtr.Add(NewStr(a.Arg[i])); } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; return *this; } //////////////////////////////////////////////////////////////// #if HAS_SHARED_MIME #include "GFilterUtils.h" #include "mime-types.h" class LSharedMime : public LLibrary { public: LSharedMime() : #ifdef _DEBUG LLibrary("libsharedmime1d") #else LLibrary("libsharedmime1") #endif { } DynFunc0(int, mimetypes_init); DynFunc1(const char*, mimetypes_set_default_type, const char *, default_type); DynFunc2(const char*, mimetypes_get_file_type, const char*, pathname, mimetypes_flags, flags); DynFunc2(const char*, mimetypes_get_data_type, const void*, data, int, length); DynFunc3(bool, mimetypes_decode, const char *, type, char **, media_type, char **, sub_type); DynFunc2(char *, mimetypes_convert_filename, const char *, pathname, const char *, mime_type); DynFunc3(bool, mimetypes_add_mime_dir, const char *, path, bool, high_priority, bool, rescan); DynFunc2(const char *, mimetypes_get_type_info, const char *, mime_type, const char *, lang); }; #endif ///////////////////////////////////////////////////////////////////////////// // // Attempts to cleanup and call drkonqi to process the crash // static LString CrashHandlerApp; void LgiCrashHandler(int Sig) { // Don't get into an infinite loop signal(SIGSEGV, SIG_DFL); #ifndef _MSC_VER // Our pid LString pid; pid.Printf("%i", getpid()); LgiTrace("LgiCrashHandler trigger pid=%s\n", pid.Get()); auto child = fork(); if (!child) { LFile::Path workingDir = CrashHandlerApp; workingDir--; chdir(workingDir); const char *args[] = { CrashHandlerApp, "--pid", pid, NULL }; execvp(CrashHandlerApp, args); exit(0); } if (LAppInst->InThread()) { LgiTrace("LgiCrashHandler showing dlg\n"); LgiMsg(NULL, "Application crashed... dumping details.", "Crash"); } else { LgiTrace("LgiCrashHandler called from worker thread.\n"); LSleep(10000); // Wait for the crash handler to do it's thing... } #endif exit(-1); } ///////////////////////////////////////////////////////////////////////////// #ifndef XK_Num_Lock #define XK_Num_Lock 0xff7f #endif #ifndef XK_Shift_Lock #define XK_Shift_Lock 0xffe6 #endif #ifndef XK_Caps_Lock #define XK_Caps_Lock 0xffe5 #endif struct Msg { LViewI *v; int m; LMessage::Param a, b; void Set(LViewI *V, int M, LMessage::Param A, LMessage::Param B) { v = V; m = M; a = A; b = B; } }; // Out of thread messages... must lock before access. class LMessageQue : public LMutex { public: typedef LArray MsgArray; LMessageQue() : LMutex("LMessageQue") { } MsgArray *Lock(const char *file, int line) { if (!LMutex::Lock(file, line)) return NULL; return &q; } operator bool() { return q.Length() > 0; } private: MsgArray q; } MsgQue; ///////////////////////////////////////////////////////////////////////////// LSkinEngine *LApp::SkinEngine = 0; LApp *TheApp = 0; LMouseHook *LApp::MouseHook = 0; LApp::LApp(OsAppArguments &AppArgs, const char *name, LAppArguments *Args) : OsApplication(AppArgs.Args, AppArgs.Arg) { TheApp = this; d = new LAppPrivate(this); Name(name); LgiArgsAppPath = AppArgs.Arg[0]; if (LIsRelativePath(LgiArgsAppPath)) { char Cwd[MAX_PATH_LEN]; getcwd(Cwd, sizeof(Cwd)); LMakePath(Cwd, sizeof(Cwd), Cwd, LgiArgsAppPath); LgiArgsAppPath = Cwd; } int WCharSz = sizeof(wchar_t); #if defined(_MSC_VER) LAssert(WCharSz == 2); ::LFile::Path Dlls(LgiArgsAppPath); Dlls--; SetDllDirectoryA(Dlls); #else LAssert(WCharSz == 4); #endif #ifdef _MSC_VER SetEnvironmentVariable(_T("GTK_CSD"), _T("0")); #else setenv("GTK_CSD", "0", true); #endif // We want our printf's NOW! setvbuf(stdout, (char*)NULL,_IONBF, 0); // print mesgs immediately. // Setup the file and graphics sub-systems d->FileSystem = new LFileSystem; d->GdcSystem = new GdcDevice; SetAppArgs(AppArgs); srand(LCurrentTime()); LColour::OnChange(); Gtk::gchar id[256]; sprintf_s(id, sizeof(id), "com.memecode.%s", name); d->App = Gtk::gtk_application_new(id, Gtk::G_APPLICATION_FLAGS_NONE); LAssert(d->App != NULL); MouseHook = new LMouseHook; // Setup the SIGSEGV signal to call the crash handler if (!GetOption("nch")) { auto programName = "crash-handler"; LFile::Path p(LSP_APP_INSTALL); p += programName; if (!p.Exists()) { // Check alternate location for development builds Dl_info dlInfo; dladdr((const void*)LgiCrashHandler, &dlInfo); if (dlInfo.dli_sname != NULL && dlInfo.dli_saddr != NULL) { p = dlInfo.dli_fname; p += "../../src/linux/CrashHandler"; p += programName; printf("Alternative path %s: %s\n", p.Exists() ? "found" : "missing", p.GetFull().Get()); } } if (p.Exists()) { CrashHandlerApp = p; signal(SIGSEGV, LgiCrashHandler); LgiTrace("Crash handler: '%s' installed.\n", CrashHandlerApp.Get()); } else { LgiTrace("Crash handler: No crash handler '%s' found, SIGSEGV handler not installed.\n", p.GetFull().Get()); } } else { LgiTrace("Crash handler: disabled.\n"); } d->GetConfig(); // System font setup LFontType SysFontType; Gtk::PangoFontMap *fm = Gtk::pango_cairo_font_map_get_default(); if (fm) { using namespace Gtk; auto cfm = PANGO_CAIRO_FONT_MAP(fm); #ifdef MAC double Dpi = 80.0; #else double Dpi = 96.0; #endif ::LFile::Path p(LSP_APP_ROOT); p += "lgi-conf.json"; if (p.IsFile()) { ::LFile f(p, O_READ); LJson j(f.Read()); auto sDpi = j.Get("DPI"); if (sDpi) Dpi = sDpi.Float(); } pango_cairo_font_map_set_resolution(cfm, Dpi); } if (SysFontType.GetSystemFont("System")) { SystemNormal = SysFontType.Create(); if (SystemNormal) SystemNormal->Transparent(true); SystemBold = SysFontType.Create(); if (SystemBold) { SystemBold->Bold(true); SystemBold->Transparent(true); SystemBold->Create(); } } else { printf("%s:%i - Couldn't get system font setting.\n", __FILE__, __LINE__); } if (!SystemNormal) { LgiMsg(0, "Error: Couldn't create system font.", "Lgi Error: LApp::LApp", MB_OK); LExitApp(); } if (!GetOption("noskin")) { extern LSkinEngine *CreateSkinEngine(LApp *App); SkinEngine = CreateSkinEngine(this); } } LApp::~LApp() { CommonCleanup(); DeleteObj(AppWnd); DeleteObj(SystemNormal); DeleteObj(SystemBold); DeleteObj(SkinEngine); DeleteObj(MouseHook); DeleteObj(d->FileSystem); DeleteObj(d->GdcSystem); DeleteObj(LFontSystem::Me); DeleteObj(d); TheApp = NULL; } LApp *LApp::ObjInstance() { return TheApp; } bool LApp::IsOk() { bool Status = #ifndef __clang__ (this != 0) && #endif (d != 0); LAssert(Status); return Status; } LMouseHook *LApp::GetMouseHook() { return MouseHook; } int LApp::GetMetric(LSystemMetric Metric) { switch (Metric) { case LGI_MET_DECOR_X: return 8; case LGI_MET_DECOR_Y: return 8 + 19; case LGI_MET_DECOR_CAPTION: return 19; default: break; } return 0; } LViewI *LApp::GetFocus() { // GtkWidget *w = gtk_window_get_focus(GtkWindow *window); return NULL; } OsThread LApp::_GetGuiThread() { return d->GuiThread; } OsThreadId LApp::GetGuiThreadId() { return d->GuiThreadId; } OsProcessId LApp::GetProcessId() { #ifdef WIN32 return GetCurrentProcessId(); #else return getpid(); #endif } OsAppArguments *LApp::GetAppArgs() { return IsOk() ? &d->Args : 0; } void LApp::SetAppArgs(OsAppArguments &AppArgs) { if (IsOk()) { d->Args = AppArgs; } } bool LApp::InThread() { OsThreadId Me = GetCurrentThreadId(); OsThreadId Gui = GetGuiThreadId(); // printf("Me=%i Gui=%i\n", Me, Gui); return Gui == Me; } struct GtkIdle { LAppPrivate *d; LAppI::OnIdleProc cb; void *param; }; Gtk::gboolean IdleWrapper(Gtk::gpointer data) { #if 0 static int64 ts = LCurrentTime(); static int count = 0; int64 now = LCurrentTime(); if (now - ts > 300) { printf("IdleWrapper = %i\n", count); count = 0; ts = now; } else count++; #endif GtkIdle *i = (GtkIdle*) data; if (i->cb) i->cb(i->param); LMessageQue::MsgArray *Msgs; if (MsgQue && (Msgs = MsgQue.Lock(_FL))) { // printf("IdleWrapper start %i\n", (int)Msgs->Length()); // Copy the messages out of the locked structure.. // This allows new messages to arrive independent // of us processing them here... LMessageQue::MsgArray q = *Msgs; Msgs->Empty(); MsgQue.Unlock(); for (auto m : q) { if (!LView::LockHandler(m.v, LView::OpExists)) { // LgiTrace("%s:%i - Invalid view: %p.\n", _FL, m.v); } else { LMessage Msg(m.m, m.a, m.b); // LgiTrace("%s::OnEvent %i,%i,%i\n", m.v->GetClass(), m.m, m.a, m.b); m.v->OnEvent(&Msg); } } } else { // printf("IdleWrapper start no lock\n"); } // printf("IdleWrapper end\n"); return i->cb != NULL; // return false; } static GtkIdle idle = {0}; bool LApp::Run(OnIdleProc IdleCallback, void *IdleParam) { if (!InThread()) { printf("%s:%i - Error: Out of thread.\n", _FL); return false; } if (!idle.d) { idle.d = d; idle.cb = IdleCallback; idle.param = IdleParam; } static bool CmdLineDone = false; if (!CmdLineDone) { CmdLineDone = true; OnCommandLine(); } Gtk::gtk_main(); return true; } bool LApp::Yield() { Gtk::gtk_main_iteration_do(false); return true; } void LApp::Exit(int Code) { if (Code) { // hard exit ::exit(Code); } else { // soft exit Gtk::gtk_main_quit(); if (d->IdleId.Length() > 0) { size_t Last = d->IdleId.Length() - 1; Gtk::g_source_remove(d->IdleId[Last]); d->IdleId.Length(Last); } } } bool LApp::PostEvent(LViewI *View, int Msg, LMessage::Param a, LMessage::Param b) { auto q = MsgQue.Lock(_FL); if (!q) { printf("%s:%i - Couldn't lock app.\n", _FL); return false; } q->New().Set(View, Msg, a, b); #if 1 // defined(_DEBUG) if (q->Length() > 200 && (q->Length()%20)==0) { static uint64 prev = 0; auto now = LCurrentTime(); if (now - prev >= 1000) { prev = now; #if defined(WIN32) char s[256]; sprintf_s(s, sizeof(s), #else printf( #endif "PostEvent Que=" LPrintfSizeT " (msg=%i)\n", q->Length(), Msg); #if defined(WIN32) OutputDebugStringA(s); #endif #ifdef LINUX LHashTbl, size_t> MsgCounts; for (auto &msg: *q) MsgCounts.Add(msg.m, MsgCounts.Find(msg.m) + 1); for (auto c: MsgCounts) printf(" %i->%i\n", c.key, (int)c.value); if (Msg == 916) { int asd=0; } #endif } } #endif MsgQue.Unlock(); // g_idle_add((GSourceFunc)IdleWrapper, &idle); g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)IdleWrapper, &idle, NULL); return true; } void LApp::OnUrl(const char *Url) { if (AppWnd) AppWnd->OnUrl(Url); } void LApp::OnReceiveFiles(::LArray &Files) { if (AppWnd) AppWnd->OnReceiveFiles(Files); else LAssert(!"You probably want to set 'AppWnd' before calling LApp::Run... maybe."); } const char *LApp::KeyModFlags::FlagName(int Flag) { #define CHECK(f) if (Flag & f) return #f; CHECK(GDK_SHIFT_MASK) CHECK(GDK_LOCK_MASK) CHECK(GDK_CONTROL_MASK) CHECK(GDK_MOD1_MASK) CHECK(GDK_MOD2_MASK) CHECK(GDK_MOD3_MASK) CHECK(GDK_MOD4_MASK) CHECK(GDK_MOD5_MASK) CHECK(GDK_BUTTON1_MASK) CHECK(GDK_BUTTON2_MASK) CHECK(GDK_BUTTON3_MASK) CHECK(GDK_BUTTON4_MASK) CHECK(GDK_BUTTON5_MASK) CHECK(GDK_SUPER_MASK) CHECK(GDK_HYPER_MASK) CHECK(GDK_META_MASK) CHECK(GDK_RELEASE_MASK) #undef CHECK return NULL; } int LApp::KeyModFlags::FlagValue(const char *Name) { #define CHECK(f) if (!Stricmp(Name, #f)) return f; CHECK(GDK_SHIFT_MASK) CHECK(GDK_LOCK_MASK) CHECK(GDK_CONTROL_MASK) CHECK(GDK_MOD1_MASK) CHECK(GDK_MOD2_MASK) CHECK(GDK_MOD3_MASK) CHECK(GDK_MOD4_MASK) CHECK(GDK_MOD5_MASK) CHECK(GDK_BUTTON1_MASK) CHECK(GDK_BUTTON2_MASK) CHECK(GDK_BUTTON3_MASK) CHECK(GDK_BUTTON4_MASK) CHECK(GDK_BUTTON5_MASK) CHECK(GDK_SUPER_MASK) CHECK(GDK_HYPER_MASK) CHECK(GDK_META_MASK) CHECK(GDK_RELEASE_MASK) #undef CHECK return 0; } ::LString LApp::KeyModFlags::FlagsToString(int s) { ::LString::Array a; for (int i=0; i<32; i++) { if (((1 << i) & s) != 0) a.New() = FlagName(1 << i); } return ::LString(",").Join(a); } LApp::KeyModFlags *LApp::GetKeyModFlags() { return d->GetModFlags(); } const char *LApp::GetArgumentAt(int n) { return n >= 0 && n < d->Args.Args ? NewStr(d->Args.Arg[n]) : 0; } bool LApp::GetOption(const char *Option, char *Dest, int DestLen) { ::LString Buf; if (GetOption(Option, Buf)) { if (Dest) { if (DestLen > 0) { strcpy_s(Dest, DestLen, Buf); } else return false; } return true; } return false; } bool LApp::GetOption(const char *Option, LString &Buf) { if (IsOk() && Option) { auto OptLen = strlen(Option); for (int i=1; iArgs.Args; i++) { auto a = d->Args.Arg[i]; if (!a) continue; if (strchr("-/\\", a[0])) { if (strnicmp(a+1, Option, OptLen) == 0) { const char *Arg = 0; if (strlen(a+1+OptLen) > 0) { Arg = a + 1 + OptLen; } else if (i < d->Args.Args - 1) { Arg = d->Args.Arg[i + 1]; } if (Arg) { if (strchr("\'\"", *Arg)) { char Delim = *Arg++; char *End = strchr(Arg, Delim); if (End) { auto Len = End-Arg; if (Len > 0) Buf.Set(Arg, Len); else return false; } else return false; } else { Buf = Arg; } } return true; } } } } return false; } void LApp::OnCommandLine() { ::LArray Files; for (int i=1; iArgs; i++) { auto a = GetAppArgs()->Arg[i]; if (LFileExists(a)) Files.Add(NewStr(a)); } // call app if (Files.Length() > 0) OnReceiveFiles(Files); // clear up Files.DeleteArrays(); } ::LString LApp::GetFileMimeType(const char *File) { ::LString Status; char Full[MAX_PATH_LEN] = ""; if (!LFileExists(File)) { // Look in the path LToken p(getenv("PATH"), LGI_PATH_SEPARATOR); for (int i=0; i 0) Status = s.SplitDelimit(":").Last().Strip(); } return Status; } } #endif #if HAS_LIB_MAGIC static bool MagicError = false; if (d->MagicLock.Lock(_FL)) { if (!d->hMagic && !MagicError) { d->hMagic = magic_open(MAGIC_MIME_TYPE); if (d->hMagic) { if (magic_load(d->hMagic, NULL) != 0) { printf("%s:%i - magic_load failed - %s\n", _FL, magic_error(d->hMagic)); magic_close(d->hMagic); d->hMagic = NULL; MagicError = true; } } else { printf("%s:%i - magic_open failed.\n", _FL); MagicError = true; } } if (d->hMagic && !MagicError) { const char *mt = magic_file(d->hMagic, File); if (mt) { Status = mt; } } d->MagicLock.Unlock(); if (Status) return Status; } #endif #if HAS_SHARED_MIME // freedesktop.org rocks... if (!d->Sm) { // Not loaded, go and try to load it... d->Sm = new LSharedMime; if (d->Sm && d->Sm->IsLoaded()) { d->Sm->mimetypes_init(); } } if (d->Sm && d->Sm->IsLoaded()) { // Loaded... char *m = (char*)d->Sm->mimetypes_get_file_type(File, MIMETYPES_CHECK_ALL); if (m) { #if HAS_FILE_CMD if (stricmp(m, "application/x-not-recognised") != 0) #endif { strcpy(Mime, m); return true; } } else { printf("%s:%i - mimetypes_get_file_type failed for '%s'\n", __FILE__, __LINE__, File); } } else { printf("%s:%i - Shared Mime not loaded!!!\n", __FILE__, __LINE__); } #endif #if HAS_FILE_CMD // doh! not installed... :( LStringPipe Output; char Args[256]; sprintf(Args, "-i \"%s\"", File); LSubProcess p("file", Args); if (p.Start()) { p.Communicate(&Output); char *Out = Output.NewStr(); if (Out) { char *s = strchr(Out, ':'); if (s && strchr(Out, '/')) { s += 2; char *e = s; while ( *e && ( IsAlpha(*e) || IsDigit(*e) || strchr(".-_/", *e) ) ) e++; *e = 0; Status.Reset(NewStr(s)); } DeleteArray(Out); } } #endif return Status; } bool LApp::GetAppsForMimeType(const char *Mime, LArray<::LAppInfo> &Apps) { // Find alternative version of the MIME type (e.g. x-type and type). char AltMime[256]; strcpy(AltMime, Mime); char *s = strchr(AltMime, '/'); if (s) { s++; int Len = strlen(s) + 1; if (strnicmp(s, "x-", 2) == 0) { memmove(s, s+2, Len - 2); } else { memmove(s+2, s, Len); s[0] = 'x'; s[1] = '-'; } } LGetAppsForMimeType(Mime, Apps); LGetAppsForMimeType(AltMime, Apps); return Apps.Length() > 0; } #if defined(LINUX) LLibrary *LApp::GetWindowManagerLib() { if (this != NULL && !d->WmLib) { char Lib[32]; WindowManager Wm = LGetWindowManager(); switch (Wm) { case WM_Kde: strcpy(Lib, "liblgikde3"); break; case WM_Gnome: strcpy(Lib, "liblgignome2"); break; default: strcpy(Lib, "liblgiother"); break; } #ifdef _DEBUG strcat(Lib, "d"); #endif d->WmLib = new LLibrary(Lib, true); if (d->WmLib) { if (d->WmLib->IsLoaded()) { Proc_LgiWmInit WmInit = (Proc_LgiWmInit) d->WmLib->GetAddress("LgiWmInit"); if (WmInit) { WmInitParams Params; // Params.Dsp = XObject::XDisplay(); Params.Args = d->Args.Args; Params.Arg = d->Args.Arg; WmInit(&Params); } // else printf("%s:%i - Failed to find method 'LgiWmInit' in WmLib.\n", __FILE__, __LINE__); } // else printf("%s:%i - couldn't load '%s.so'\n", __FILE__, __LINE__, Lib); } // else printf("%s:%i - alloc error\n", __FILE__, __LINE__); } return d->WmLib && d->WmLib->IsLoaded() ? d->WmLib : 0; } void LApp::DeleteMeLater(LViewI *v) { d->DeleteLater.Add(v); } void LApp::SetClipBoardContent(OsView Hnd, ::LVariant &v) { // Store the clipboard data we will serve d->ClipData = v; } bool LApp::GetClipBoardContent(OsView Hnd, ::LVariant &v, ::LArray &Types) { return false; } #endif LSymLookup *LApp::GetSymLookup() { return d; } bool LApp::IsElevated() { #ifdef WIN32 LAssert(!"What API works here?"); return false; #else return geteuid() == 0; #endif } int LApp::GetCpuCount() { - return 1; + return sysconf(_SC_NPROCESSORS_ONLN); } LFontCache *LApp::GetFontCache() { if (!d->FontCache) d->FontCache.Reset(new LFontCache(SystemNormal)); return d->FontCache; } #ifdef LINUX LApp::DesktopInfo::DesktopInfo(const char *file) { File = file; Dirty = false; if (File) Serialize(false); } bool LApp::DesktopInfo::Serialize(bool Write) { ::LFile f; if (Write) { ::LFile::Path p(File); p--; if (!p.Exists()) return false; } else if (!LFileExists(File)) return false; if (!f.Open(File, Write?O_WRITE:O_READ)) { LgiTrace("%s:%i - Failed to open '%s'\n", _FL, File.Get()); return false; } if (Write) { f.SetSize(0); for (unsigned i=0; i= 0) { int e = l.Find("]", ++s); if (e >= 0) { Cur = &Data.New(); Cur->Name = l(s, e - s + 1); } } else if ((s = l.Find("=")) >= 0) { if (!Cur) Cur = &Data.New(); KeyPair &kp = Cur->Values.New(); kp.Key = l(0, s).Strip(); kp.Value = l(++s, -1).Strip(); // printf("Read '%s': '%s'='%s'\n", Cur->Name.Get(), kp.Key.Get(), kp.Value.Get()); } } } return true; } LApp::DesktopInfo::Section *LApp::DesktopInfo::GetSection(const char *Name, bool Create) { for (unsigned i=0; iGet(Field, false, Dirty); if (kp) { return kp->Value; } } } return ::LString(); } bool LApp::DesktopInfo::Set(const char *Field, const char *Value, const char *Sect) { if (!Field) return false; Section *s = GetSection(Sect ? Sect : DefaultSection, true); if (!s) return false; KeyPair *kp = s->Get(Field, true, Dirty); if (!kp) return false; if (kp->Value != Value) { kp->Value = Value; Dirty = true; } return true; } LApp::DesktopInfo *LApp::GetDesktopInfo() { auto sExe = LGetExeFile(); ::LFile::Path Exe(sExe); ::LFile::Path Desktop(LSP_HOME); ::LString Leaf; Leaf.Printf("%s.desktop", Exe.Last().Get()); Desktop += ".local/share/applications"; Desktop += Leaf; const char *Ex = Exe; const char *Fn = Desktop; if (d->DesktopInfo.Reset(new DesktopInfo(Desktop))) { // Do a sanity check... ::LString s = d->DesktopInfo->Get("Name"); if (!s && Name()) d->DesktopInfo->Set("Name", Name()); s = d->DesktopInfo->Get("Exec"); if (!s || s != (const char*)sExe) d->DesktopInfo->Set("Exec", sExe); s = d->DesktopInfo->Get("Type"); if (!s) d->DesktopInfo->Set("Type", "Application"); s = d->DesktopInfo->Get("Categories"); if (!s) d->DesktopInfo->Set("Categories", "Application;"); s = d->DesktopInfo->Get("Terminal"); if (!s) d->DesktopInfo->Set("Terminal", "false"); d->DesktopInfo->Update(); } return d->DesktopInfo; } bool LApp::SetApplicationIcon(const char *FileName) { DesktopInfo *di = GetDesktopInfo(); if (!di) return false; ::LString IcoPath = di->Get("Icon"); if (IcoPath == FileName) return true; di->Set("Icon", FileName); return di->Update(); } #endif //////////////////////////////////////////////////////////////// OsApplication *OsApplication::Inst = 0; class OsApplicationPriv { public: OsApplicationPriv() { } }; OsApplication::OsApplication(int Args, const char **Arg) { Inst = this; d = new OsApplicationPriv; } OsApplication::~OsApplication() { DeleteObj(d); Inst = 0; } //////////////////////////////////////////////////////////////// void LMessage::Set(int Msg, Param ParamA, Param ParamB) { m = Msg; a = ParamA; b = ParamB; } struct GlibEventParams : public LMessage { GtkWidget *w; }; bool GlibWidgetSearch(GtkWidget *p, GtkWidget *w, bool Debug, int depth) { char indent[256] = ""; if (Debug) { int ch = depth * 2; memset(indent, ' ', ch); indent[ch] = 0; printf("%sGlibWidgetSearch: %p, %p\n", indent, p, w); } if (p == w) return true; if (GTK_IS_CONTAINER(p)) { int n = 0; Gtk::GList *top = gtk_container_get_children(GTK_CONTAINER(p)); Gtk::GList *i = top; while (i) { if (Debug) printf("%s[%i]=%s\n", indent, n, gtk_widget_get_name((GtkWidget*)i->data)); if (i->data == w) return true; else if (GlibWidgetSearch((GtkWidget*)i->data, w, Debug, depth + 1)) return true; i = i->next; n++; } g_list_free(top); } else if (GTK_IS_BIN(p)) { GtkWidget *child = gtk_bin_get_child(GTK_BIN(p)); if (child) { if (Debug) printf("%schild=%s\n", indent, gtk_widget_get_name(child)); if (child == w) return true; else if (GlibWidgetSearch(child, w, Debug, depth + 1)) return true; } } else if (Debug) { printf("%sUnknown=%s\n", indent, gtk_widget_get_name(p)); } return false; } void LApp::OnDetach(LViewI *View) { LMessageQue::MsgArray *q = MsgQue.Lock(_FL); if (!q) { printf("%s:%i - Couldn't lock app.\n", _FL); return; } MsgQue.Unlock(); } bool LMessage::Send(LViewI *View) { if (!View) { LAssert(!"No view"); return false; } return LAppInst->PostEvent(View, m, a, b); }