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);
}