diff --git a/HtmlTest/TestSuite.cpp b/HtmlTest/TestSuite.cpp --- a/HtmlTest/TestSuite.cpp +++ b/HtmlTest/TestSuite.cpp @@ -1,631 +1,631 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Html.h" #include "lgi/common/List.h" #include "lgi/common/DateTime.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Scripting.h" #include "lgi/common/Box.h" #include "lgi/common/TextView3.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/Combo.h" #include "lgi/common/Net.h" #include "lgi/common/Http.h" #include "lgi/common/Filter.h" #include "lgi/common/NetTools.h" #include "lgi/common/ImageComparison.h" #include "lgi/common/OpenSSLSocket.h" #include "lgi/common/Net.h" #include "lgi/common/EmojiFont.h" #include "lgi/common/Http.h" #include "lgi/common/Menu.h" #include "resdefs.h" #define HAS_LOG_VIEW 0 #define HAS_IMAGE_LOADER 1 enum Controls { IDC_HTML = 100, IDC_LOG, IDC_LIST }; class FileInf { public: char *File; LDateTime Date; FileInf() { File = 0; } ~FileInf() { DeleteArray(File); } }; int InfCmp(FileInf *a, FileInf *b, NativeInt d) { return -a->Date.Compare(&b->Date); } class HtmlItem : public LListItem { char *Base; public: HtmlItem(char *b, char *n) { Base = b; SetText(n); } void OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LSubMenu s; s.AppendItem("Copy Path", 100); m.ToScreen(); if (s.Float(GetList(), m.x, m.y, m.Left()) == 100) { LClipBoard c(GetList()); char Path[MAX_PATH_LEN]; LMakePath(Path, sizeof(Path), Base, GetText(0)); c.Text(Path); } } } }; class HtmlScriptContext : #ifdef _GHTML2_H public Html2::LHtml2, #else public Html1::LHtml, #endif public LScriptContext { LScriptEngine *Eng; public: - static GHostFunc Methods[]; + static LHostFunc Methods[]; HtmlScriptContext(int Id, LDocumentEnv *Env) : #ifdef _GHTML2_H Html2::LHtml2 #else Html1::LHtml #endif (Id, 0, 0, 100, 100, Env) { Eng = NULL; LFont *f = new LFont; if (f) { f->Face("Times New Roman"); f->PointSize(12); // Size '16' is about 12 pt. Maybe they mean 16px? SetFont(f, true); } } - GHostFunc *GetCommands() + LHostFunc *GetCommands() { return Methods; } void SetEngine(LScriptEngine *Eng) { } char *GetIncludeFile(char *FileName) { return NULL; } LAutoString GetDataFolder() { return LAutoString(); } }; -GHostFunc HtmlScriptContext::Methods[] = +LHostFunc HtmlScriptContext::Methods[] = { - GHostFunc(0, 0, 0), + LHostFunc(0, 0, 0), }; class HtmlImageLoader : public LThread, public LMutex, public LCancel { LArray In; public: HtmlImageLoader() : LThread("HtmlImageLoader.Thread"), LMutex("HtmlImageLoader.Mutex") { Run(); } ~HtmlImageLoader() { Cancel(true); while (!IsExited()) LSleep(1); } void Add(LDocumentEnv::LoadJob *j) { if (Lock(_FL)) { In.Add(j); Unlock(); } } LAutoPtr CreateSock(const char *Proto) { LAutoPtr s; if (Proto && !_stricmp(Proto, "https")) { SslSocket *ss; s.Reset(ss = new SslSocket); ss->SetSslOnConnect(false); } else s.Reset(new LSocket); return s; } int Main() { while (!IsCancelled()) { LAutoPtr j; if (Lock(_FL)) { if (In.Length()) { j.Reset(In[0]); In.DeleteAt(0, true); } Unlock(); } LDocumentEnv::LoadJob *Job = dynamic_cast(j.Get()); if (Job) { LUri u(Job->Uri); if (u.IsFile()) { // Local document? if (Job->pDC.Reset(GdcD->Load(Job->Uri))) { LDocumentEnv *e = Job->Env; if (e) { // LgiTrace("Loaded '%s' as image %ix%i\n", u.Path, j->pDC->X(), j->pDC->Y()); e->OnDone(j); } } } else { LMemQueue p(1024); LString Err; auto r = LgiGetUri(this, &p, &Err, Job->Uri); if (r) { uchar Hint[16]; p.Peek(Hint, sizeof(Hint)); auto Filter = LFilterFactory::New(u.sPath, FILTER_CAP_READ, Hint); if (Filter) { LAutoPtr Img(new LMemDC); LFilter::IoStatus Rd = Filter->ReadImage(Img, &p); if (Rd == LFilter::IoSuccess) { Job->pDC = Img; if (Job->Env) { // LgiTrace("Loaded '%s' as image %ix%i\n", u.Path, j->pDC->X(), j->pDC->Y()); Job->Env->OnDone(j); } else { LgiTrace("%s:%i - No env for '%s'\n", _FL, u.sPath.Get()); LAssert(0); } } else LgiTrace("%s:%i - Failed to read '%s'\n", _FL, u.sPath.Get()); } else LgiTrace("%s:%i - Failed to find filter for '%s'\n", _FL, u.sPath.Get()); } else LgiTrace("%s:%i - Failed to get '%s'\n", _FL, Job->Uri.Get()); } } else LSleep(10); } return 0; } }; class AppWnd : public LWindow, public LDefaultDocumentEnv { LList *Lst; HtmlScriptContext *Html; LTextView3 *Text; char Base[256]; LAutoPtr Script; LAutoPtr Worker; LAutoPtr Emoji; LoadType GetContent(LoadJob *&j) { LUri u(j->Uri); if (!u.sProtocol) { char p[MAX_PATH_LEN]; if (LMakePath(p, sizeof(p), Base, j->Uri) && LFileExists(p)) { LString Ext = LGetExtension(p); if (Ext.Equals("css") || Ext.Equals("html")) { LAutoPtr f(new LFile); if (f && f->Open(p, O_READ)) { j->Stream.Reset(f.Release()); return LoadImmediate; } } else { j->pDC.Reset(GdcD->Load(p)); if (j->pDC) return LoadImmediate; } return LoadError; } } #if HAS_IMAGE_LOADER if (!Worker) Worker.Reset(new HtmlImageLoader); Worker->Add(j); j = NULL; return LoadDeferred; #else return LoadNotImpl; // GDefaultDocumentEnv::GetContent(j); #endif } public: AppWnd() { Html = 0; Lst = 0; Text = NULL; Base[0] = 0; Emoji.Reset(new LEmojiFont()); Name("Html Test Suite"); SetQuitOnClose(true); if (Attach(0)) { Menu = new LMenu(); if (Menu) { Menu->Attach(this); Menu->Load(this, "IDM_MENU"); } LBox *s = new LBox; if (s) { AddView(s); s->AddView(Lst = new LList(IDC_LIST, 0, 0, 100, 100)); Lst->Sunken(false); Lst->AddColumn("File", 400); Lst->CssStyles("width: 200px;"); s->Value(200); #if HAS_LOG_VIEW LBox *vert = new LBox; vert->SetVertical(true); s->AddView(vert); vert->AddView(Html = new HtmlScriptContext(IDC_HTML, this)); Html->SetCssStyle("height: 70%;"); vert->AddView(Text = new LTextView3(IDC_LOG, 0, 0, 100, 100)); LBox::Spacer *sp = s->GetSpacer(0); if (sp) { sp->Colour.Rgb(64, 64, 64); sp->SizePx = 2; } sp = vert->GetSpacer(0); if (sp) { sp->Colour.Rgb(64, 64, 64); sp->SizePx = 2; } #else s->AddView(Html = new HtmlScriptContext(IDC_HTML, this)); #endif Script.Reset(new LScriptEngine(this, Html, NULL)); if (Html) Html->SetEnv(this); #if HAS_IMAGE_LOADER Html->SetLoadImages(true); #endif if (sprintf_s(Base, sizeof(Base), "%s", LGetExePath().Get()) > 0) { #if defined(WIN32) if (stristr(Base, "Release") || stristr(Base, "Debug")) LTrimDir(Base); #endif #if defined(MAC) && defined(__GTK_H__) LMakePath(Base, sizeof(Base), Base, "../../../.."); #endif List Files; LDirectory *d = new LDirectory; if (d) { LMakePath(Base, sizeof(Base), Base, "Files"); for (bool b = d->First(Base); b; b = d->Next()) { if (!d->IsDir() && MatchStr("*.html", d->GetName())) { char p[256]; if (d->Path(p, sizeof(p))) { FileInf *f = new FileInf; if (f) { f->File = NewStr(p); f->Date.Set(d->GetLastWriteTime()); Files.Insert(f); } } } } DeleteObj(d); Files.Sort(InfCmp); for (auto f: Files) { char *d = strrchr(f->File, DIR_CHAR); if (d) { HtmlItem *i = new HtmlItem(Base, d + 1); if (i) Lst->Insert(i); } } Files.DeleteObjects(); } } } LRect r(0, 0, 1200, 800); SetPos(r); MoveToCenter(); AttachChildren(); Visible(true); OnNotify(FindControl(IDC_LIST), LNotifyItemSelect); } else LExitApp(); } ~AppWnd() { } int OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDM_SAVE_IMGS: { LPoint PageSize(1000, 2000); LProgressDlg Prog(this, true); Prog.SetDescription("Scanning for HTML..."); char p[MAX_PATH_LEN]; LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p)); LArray Ext; LArray Files; Ext.Add("*.html"); Ext.Add("*.htm"); if (LRecursiveFileSearch(p, &Ext, &Files)) { LDateTime Now; Now.SetNow(); char OutPath[MAX_PATH_LEN], NowStr[32]; sprintf_s( NowStr, sizeof(NowStr), "%.4i-%.2i-%.2i_%.2i-%.2i-%.2i", Now.Year(), Now.Month(), Now.Day(), Now.Hours(), Now.Minutes(), Now.Seconds()); LMakePath(OutPath, sizeof(OutPath), p, "Output"); if (!LDirExists(OutPath)) FileDev->CreateFolder(OutPath); LMakePath(OutPath, sizeof(OutPath), OutPath, NowStr); if (!LDirExists(OutPath)) FileDev->CreateFolder(OutPath); Prog.SetDescription("Saving renders..."); Prog.SetRange(Files.Length()); for (int i=0; iSave(p, &Screen)) LAssert(0); Prog.Value(i); LYield(); } Files.DeleteArrays(); } break; } case IDM_COMPARE_IMAGES: { char p[MAX_PATH_LEN]; LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p)); LArray Ext; LArray Files; Ext.Add("*.png"); // if (LRecursiveFileSearch(p, &Ext, &Files)) char OutPath[MAX_PATH_LEN]; LMakePath(OutPath, sizeof(OutPath), p, "Output"); if (!LDirExists(OutPath)) { LgiMsg(this, "No output render folder.", "Html Test Suite"); break; } new ImageCompareDlg(this, OutPath); break; } } return 0; } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_LIST: { if (n.Type == LNotifyItemSelect) { LListItem *s = Lst->GetSelected(); if (s) { char p[256]; LMakePath(p, sizeof(p), Base, s->GetText(0)); if (LFileExists(p)) { char *h = LReadTextFile(p); if (h) { if (Html) Html->Name(h); DeleteArray(h); } } } } break; } case IDC_HTML: { switch (n.Type) { case LNotifySelectionChanged: { if (Text) { LAutoString s(Html->GetSelection()); if (s) Text->Name(s); else Text->Name("(null)"); } break; } default: break; } break; } } return 0; } bool OnCompileScript(LDocView *Parent, char *Script, const char *Language, const char *MimeType) { // return Script->Compile(Code, true); return false; } bool OnExecuteScript(LDocView *Parent, char *Script) { return false; // Script->RunTemporary(Code); } }; void FontSz() { for (int i=6; i<32; i++) { LFont f; if (f.Create("verdana", LCss::Len(LCss::LenPx, i))) { double a = (double) f.GetHeight() / f.Ascent(); LgiTrace("%i: %i, ascent=%f, a=%f\n", i, f.GetHeight(), f.Ascent(), a); } } } int LgiMain(OsAppArguments &AppArgs) { LApp a(AppArgs, "HtmlTestSuite"); if (a.IsOk()) { //FontSz(); a.AppWnd = new AppWnd; a.Run(); } return 0; } diff --git a/ScriptingUnitTests/Main.cpp b/ScriptingUnitTests/Main.cpp --- a/ScriptingUnitTests/Main.cpp +++ b/ScriptingUnitTests/Main.cpp @@ -1,152 +1,152 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "../src/common/Coding/ScriptingPriv.h" #include "lgi/common/StringClass.h" #include "lgi/common/LgiRes.h" struct ConsoleLog : public LStream { ssize_t Write(const void *Ptr, ssize_t Size, int Flags) { return printf("%.*s", (int)Size, (char*)Ptr); } }; class App : public LApp, public LScriptContext, public LVmDebuggerCallback { LScriptEngine *Engine; LAutoString SrcFile; ConsoleLog Log; LVmDebugger *AttachVm(LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) { return new LVmDebuggerWnd(NULL, this, Vm, Code, Assembly); } bool CompileScript(LAutoPtr &Output, const char *FileName, const char *Source) { return false; } public: int Status; App(OsAppArguments &AppArgs) : LApp(AppArgs, "LgiScript") { LFile::Path p(LSP_APP_INSTALL); p += "Resources"; p += "LgiScript.lr8"; LgiGetResObj(true, p); Engine = NULL; Status = 0; } - GHostFunc *GetCommands() { return NULL; } + LHostFunc *GetCommands() { return NULL; } void SetEngine(LScriptEngine *Eng) { Engine = Eng; } void OnReceiveFiles(LArray &Files) { for (int i=0; iGetOption("disassemble"); if (!LFileExists(File)) { printf("Error: '%s' not found.\n", File); return false; } if (!SrcFile.Reset(NewStr(File))) { printf("Error: Mem alloc failed.\n"); return false; } LScriptEngine Eng(NULL, NULL, this); Eng.SetConsole(&Log); LAutoString Src(::LReadTextFile(SrcFile)); if (!Src) { printf("Error: Failed to read '%s'.\n", SrcFile.Get()); return false; } LAutoPtr Obj; if (!Eng.Compile(Obj, NULL, Src, File)) { printf("Error: Compilation failed '%s'.\n", SrcFile.Get()); return false; } LVariant Ret; LExecutionStatus s = Eng.Run(Obj, &Ret); if (s == ScriptError) { printf("Error: Execution failed '%s'.\n", SrcFile.Get()); return false; } else if (s == ScriptWarning) { printf("Warning: Execution succeeded with warnings '%s'.\n", SrcFile.Get()); return false; } if (Ret.CastInt32()) printf("Success: %s\n", File); else { printf("Failed: %s\n", File); s = ScriptError; } if (Disassemble) { LString f = File; int Idx = f.RFind("."); if (Idx > 0) { f = f(0, Idx) + ".asm"; LAutoString a(LReadTextFile(f)); if (a) { printf("%s\n", a.Get()); } } } return s == ScriptSuccess; } }; int LgiMain(OsAppArguments &AppArgs) { App a(AppArgs); if (a.IsOk()) { a.OnCommandLine(); } return a.Status; } \ No newline at end of file diff --git a/docs/scripting/library.html b/docs/scripting/library.html --- a/docs/scripting/library.html +++ b/docs/scripting/library.html @@ -1,1357 +1,1384 @@ Lgi Scripting Library

Lgi Scripting Library

These are the built in methods for the Lgi scripting language:
Strings Containers File System Dates and Time Bitmaps User Interface General
Global functions: LoadString
FormatSize
Sprintf
Print
ToString
New
Delete
+ Len
ReadTextFile
WriteTextFile
SelectFiles
SelectFolder
ListFiles
DeleteFile

PathExists
PathJoin
PathSep
Sleep
ClockTick
Now
CreateSurface
ColourSpaceToString
StringToColourSpace
LoadDialog
MessageDlg
GetInputDlg
GetViewById
Execute
System
OsName
OsVersion
+
CurrentScript
Assert
Throw
DebuggerEnabled
Object Members: obj.Find
obj.Split
obj.Join
obj.Upper
obj.Lower
obj.Sub
obj.Strip
obj.Add
obj.HasKey
obj.Delete
obj.Sort
obj.Open
obj.Read
obj.Write
obj.Pos
obj.Close
obj.Year
obj.Month
obj.Day
obj.Hour
obj.Minute
obj.Second
obj.Milli

obj.Date
obj.Time
obj.DateTime
obj.Timestamp
obj.x
obj.y
obj.Bits
obj.ColourSpace
obj.Palette
obj.Load
obj.Save
obj.Handle
obj.Id
obj.Name
obj.Value
obj.Enabled
obj.Foreground
obj.Background
obj.Type
obj.Length

string LoadString(id);

Loads a string from the resource file. The strings have to be stored in a Lgi resource file and loaded at runtime by the current application. There is a graphical editor for the resource files.

Arguments:
  • id - the string's id as an integer.

Returns:
  • A string or NULL on failure.

Example:
c = LoadString(186);
 Print(c, "\n");
Enter offset:

string FormatSize(bytes);

Formats a number of bytes in KB, MB or GB as appropriate.

Arguments:
  • bytes - the size to format.

Returns:
  • A string containing the size description.

Example:
c = FormatSize(345674211);
 Print(c, "\n");
329.66 M

string Sprintf(format[, args, ...]);

Formats a string. Not available on the mac platform.

Arguments:
  • format - the format of the output string, same as C's printf.
  • [optional]args - any arguments that are required to be formatted.

Returns:
  • A formatted string.

Example:
c = Sprintf("Format: %i, %i, %s, 0x%x\n", 345, 11, "StringArg", 0x1234fa);
 Print(c);
Format: 345, 11, StringArg, 0x1234af

void Print(item1[, item2, ...]);

Prints variables on the console, if available. No new line is explicitly printed, you have to add "\n" to your arguments to move to the next line.

Arguments:
  • itemN - The object to print. Non string objects will be converted to string if possible.

Returns:
  • Nothing.

String ToString([items, ...]);

Converts all the arguments to strings.

Arguments:
  • itemN - And object to print.

Returns:
  • String.

int obj.Find(sub_string[, start[, end]]);

Finds a the position of a sub-string.

Arguments:
  • sub_string - The string to find. The search is case sensitive.
  • [optional] start - The start index of the area to search.
  • [optional] end - The end index of the area to search.

Returns:
  • The location of the sub-string or -1 if not found.

Example:
c = "A string to search";
 Print(c.Find("to"), "\n");
 Print(c.Find("missing"), "\n");
9
 -1

list obj.Split(separator[, max_split]);

Splits a string into parts based on a separator.

Arguments:
  • separator - The string to use as a separator between the parts.
  • [optional] max_splits - The maximum number of splits to make.

Returns:
  • A list of strings. Could be empty if the input is also empty.

Example:
c = "123, 456, 879";
 p = c.split(",");
 for (i=0; i<p.Length; i++)
 {
     Print("[", i, "]='", p[i].Strip(), "'\n");
 }
[0]='123'
 [1]='456'
 [2]='879'

string obj.Join(list);

Joins a list of objects into a single string separated by the 'obj' string.

Arguments:
  • list - The objects to be joined.

Returns:
  • A string concatenation of all the source objects.

Example:
c = New("list");
 c.Add(123);
 c.Add(456);
 c.Add(789);
 p = ", ";
 r = p.Join(c);
 Print(r, "\n");
123, 456, 789

string obj.Upper();

Makes an upper case version of 'obj'.

Returns:
  • An upper case string.

Example:
c = "A string";
 Print(c.Upper(), "\n");
A STRING

string obj.Lower();

Makes a lower case version of 'obj'.

Returns:
  • An lower case string.

Example:
c = "A string";
 Print(c.Lower(), "\n");
a string

string obj.Sub(start[, end]);

Returns part of the string 'obj'.

Arguments:
  • start - The start of the sub-string.
  • [optional] end - The end of the sub-string. If not supplied the end of the source string is used.

Returns:
  • A string.

Example:
c = "A string";
 Print(c.Sub(2, 5), "\n");
 Print(c.Sub(4), "\n");
str
 ring

string obj.Strip([delimiters]);

Strips the specified characters off the start and end of the string.

Arguments:
  • [optional] delimiters - The characters to strip, defaults to whitespace (' \t\r\n').

Returns:
  • A string.

Example:
c = "[A string]";
 Print(c.Strip("[]"), "\n");
A String

object New(object);

Creates a new object.

Arguments:
  • object - the name of the object. Can be a built in type of a custom defined type.
    • ### - A number create a binary block of the specified number of bytes.
    • "List" - creates a list.
    • "HashTable" - creates a hash table.
    • "Surface" - creates a memory surface (bitmap) to draw on.
    • "File" - creates a file object.
    • "DateTime" - creates a date/time object.
    • <custom_type_name> - Some custom type defined in script.

Returns:
  • The new object or NULL on failure.

Example:
c = New("DateTime");
 c.Date = "1/4/2014";
 Print(c.Type(), ": ", c, "\n");
DateTime: 1/04/2014 12:00:00a

void Delete(object);

Will set any object to NULL. Must be used with custom types before they go out of scope otherwise they will leak memory. But also works on any other type of object, like lists, hashtables or basic strings and ints.

Arguments:
  • object - the object to delete.

Returns:
  • Nothing.
+

int Len(object[, object2]);

+
+ Returns the length of an array or hashtable. If more than one + object is passed it'll sum the length of all of them. Non collection + objects count as 1 thing. For instance a integer or floating point value + are "1" item. +

+ Arguments: +
    +
  • object - some object collection. +
+
+ Returns: +
    +
  • Count of items in collections. +
+
+

void obj.Add(object[, key]);

Adds 'object1' to a container. Depending on the object type:
  • List: the key is an optional integer index. If not supplied the object is appended to the end.
  • HashTable: the key is NOT optional and specifies the key under which to store the object.

Arguments:
  • object - the object to add.
  • key - the location to store the object.

Returns:
  • Nothing.

Example:
c = New("list");
 c.Add("first");
 c.Add("second");
 Print(c, "\n");
 Print(c.Length, "\n");
{first, second}
 2

bool obj.HasKey(key);

Returns true of the container has something stored at 'key':
Arguments:
  • key - the location to store the object.
    • List: the key is an integer index.
    • HashTable: the key is a string.

Returns:
  • TRUE if the object exists.

Example:
c = New("hashtable");
 c.Add(23, "first");
 c.Add(345, "second");
 Print(c.HasKey("asd"), "\n");
 Print(c.HasKey("first"), "\n");
0
 1

void obj.Delete(key);

Removes and deletes an object from the container

Arguments:
  • key - the location of the object:
    • List: the key is an integer index. If it's out of range, nothing happens.
    • HashTable: the key is string. If the key isn't present nothing happens.

Returns:
  • Nothing.

Example:
c = New("hashtable");
 c.Add(123, "first");
 c.Add(456, "second");
 Print(c.Length, "\n");
 c.Delete("first");
 Print(c.Length, "\n");
 
2
 1

void obj.Sort(key);

Sorts a list. Not relevant for a HashTable.

Arguments:
    • If the list contains strings or integers, not parameter is needed.
    • If the list contains object references, then a string defining which field of the object to sort on needs to be provided.

Returns:
  • Nothing.

Example:
c = New("list");
 c.Add("first");
 c.Add("second");
 c.Sort();
 

string ReadTextFile(filename);

Returns the contents of a text file.

Arguments:
  • filename - the name of the file

Returns:
  • the contents of the file as a string.

bool WriteTextFile(filename, data);

Writes the contents of a variable to a text file.

Arguments:
  • filename - the name of the file
  • data - the date to write, can either be a string or a binary object

Returns:
  • non-zero on succes, zero on failure.
-

list SelectFiles(parent_wnd[, file_types[, initial_dir[, multi_select[, save_as]]]]);

+

void SelectFiles(ParentWnd, Callback[, FileTypes[, InitialDir[, MultiSelect[, SaveAs]]]]);

Shows a file select dialog.

Arguments:
    -
  • parent_wnd - the handle of the parent window. -
  • [optional] file_types - the types of files to select, e.g. "*.gif;*.png;*.jpg". -
  • [optional] initial_dir - the initial folder to select from. -
  • [optional] multi_select - TRUE if multiple file can be selected. -
  • [optional] save_as - TRUE if dialog should be for saving a file. +
  • ParentWnd - the handle of the parent window. +
  • Callback - The name of the callback function as a string. +
  • [optional] FileTypes - the types of files to select, e.g. "*.gif;*.png;*.jpg". +
  • [optional] InitialDir - the initial folder to select from. +
  • [optional] MultiSelect - TRUE if multiple file can be selected. +
  • [optional] SaveAs - TRUE if dialog should be for saving a file.

- Returns: -
    -
  • list of filenames. -
+ Returns: Nothing.

Example: -
files = SelectFiles(Parent, "*.png", "C:\\Users\\matthew\\Desktop", 1);
-if (files)
+				
function OnFiles(files)
 {
-    for (i=0; i<files.Length; i++)
-    {
-        Print(files[i], "\n");
-    }
-}
-
C:\Users\matthew\Desktop\2channel.png
-C:\Users\matthew\Desktop\19.Poster_1.png
-C:\Users\matthew\Desktop\payment.png
+ Print("Files:", files, "\n"); +} + +SelectFiles(Parent, "OnFiles", "*.png", "C:\\Users\\myUserName\\Desktop", 1);
+
C:\Users\myUserName\Desktop\2channel.png
+C:\Users\myUserName\Desktop\19.Poster_1.png
+C:\Users\myUserName\Desktop\payment.png
-

list SelectFolder(parent_wnd[, initial_dir]);

+

void SelectFolder(ParentWnd, Callback[, InitialDir]);

Shows a folder select dialog.

Arguments:
    -
  • parent_wnd - the handle of the parent window. -
  • [optional] initial_dir - the initial folder to select from. +
  • ParentWnd - the handle of the parent window. +
  • Callback - the name of the callback function as a string. +
  • [optional] InitialDir - the initial folder to select from.

- Returns: -
    -
  • String FolderPath. -
+ Returns: nothing.
+
+ Example: +
function OnFolder(folder)
+{
+    Print("Folder:", folder, "\n");
+}
+				
+SelectFolder(Parent, "OnFolder", "C:\\Users\\myUserName\\Desktop");

list ListFiles(folder_path[, pattern]);

Lists files in a folder.

Arguments:
  • folder_path - the folder to list.
  • [optional] pattern - the files to list, e.g. "*.gif".

Returns:
  • list of file descriptors. You can access relavant meta-data using the fields:
    • Name: the file's name.
    • Length: the size in bytes.
    • Modified: the date modified.
    • Folder: True if the entry is a folder.

  • Example:
    files = ListFiles(".");
     if (files)
     {
         for (i=0; i<files.Length; i++)
         {
             Print(files[i].Name, " is ", files[i].Length, " bytes\n");
         }
     }
    FileSystem.asm is 1080 bytes
     FileSystem.script is 936 bytes
     Object.script is 1320 bytes
     Strings.script is 1440 bytes
     System.script is 837 bytes

bool DeleteFile(path);

Deletes a file.

Arguments:
  • path - The path to the file to delete.

Returns:
  • True if the file is deleted.

string CurrentScript();

Returns:
  • the current script file and it's path.

bool Assert(value[, msg]);

Checks that 'value' is non-zero, if it is zero, it throws an exception with 'msg' if supplied.

Arguments:
  • value - The value to check.
  • msg - [Optional] the message to pass to Throw.

Returns:
  • Bool - true if value is non-zero.

void Throw([msg]);

Throws an exception, optionally with 'msg' as the message.

Arguments:
  • msg - [Optional] the message to display in the debugger.

Returns:
  • None.

void DebuggerEnabled(enabled);

Turns the debugger on or off.

Arguments:
  • enabled - True if you want the debugger to appear on an exception. If false the script will just exit at the point the exception happened, returning "false" to the caller.

Returns:
  • None.

int PathExists(path);

Queries the file system to see if a path exists as a file or folder.

Arguments:
  • path - The path to the query.

Returns:
  • 0 - If the path doesn't exist.
  • 1 - If the path is a file.
  • 2 - If the path is a folder.

string PathJoin(path1[, path2[, ...]]);

Joins a whole lot of path fragments together, evaluating relative parts as needed.

Arguments:
  • path1 - The base path.
  • path2 - A relative path segment.

Returns:
  • The complete normalised path.

string PathSep();

Returns:
  • The path separator character for the current platform. ("\" on windows, "/" otherwise)

bool obj.Open(path[, mode]);

Opens a file. Typically this follows creating a file object with New and once the file operations are done, use Delete to clean up the memory.

Also of note: to get or change the size of the file use the Length member.

Arguments:
  • path - The path to the file to open.
  • [optional] mode - Type of access:
    • "r" - Read access (the default).
    • "w" - Write access.
    • "rw" - Read and write access.

Returns:
  • True if the file is openned.

object obj.Read(length[, type]);

Reads content from a file at the current location.

Arguments:
  • length - The bytes to read.
  • type - The type of the data:
    • 0: string data (the default).
    • 1: integer data.

Returns:
  • The data read as the indicated type, or NULL if the read failed.

int obj.Write(object[, length]);

Writes the object to the file.

Arguments:
  • object - the object to write.
  • length - [optional] number of bytes to write.
    If specified and the object is:
    • an integer: valid values are 1, 2, 4, or 8 up to the maximum size of the actual variable.
    • a string: any value from 1 through to the length of the string including the NULL in bytes (not characters).
    If not specified and the object is:
    • an integer: all the integer's bytes are written in the native order of the CPU (little-endian for supported platforms). For GV_INT32 4 bytes are written, for GV_INT64 8 bytes are written.
    • a string: all the string's bytes up to, but not including, the NULL.

Returns:
  • The bytes written.

int obj.Pos([new_position]);

Gets or sets the current file position.

Arguments:
  • [optional] new_position - A new file position.

Returns:
  • The current file position.

void obj.Close();

Closes a file.

Returns:
  • Nothing.

void Sleep(ms);

Pauses the current thread.

Arguments:
  • ms - The number of milliseconds to pause for.

Returns:
  • Nothing.

int64 ClockTick();

Returns the current clock tick.

Arguments:
  • None.

Returns:
  • A 64bit clock time from an unknown epoch, in milliseconds. Useful for timing things.

datetime Now();

Returns the current date time.

Arguments:
  • None.

Returns:
  • A GDateTime variant set to the current date and time.

int obj.Year;

Sets or gets the year part of a date/time object.

int obj.Month;

Sets or gets the month part of a date/time object.

int obj.Day;

Sets or gets the day part of a date/time object.

int obj.Hour;

Sets or gets the hour part of a date/time object.

int obj.Minute;

Sets or gets the minute part of a date/time object.

int obj.Second;

Sets or gets the second part of a date/time object.

int obj.Milli;

Sets or gets the millisecond part of a date/time object.

int64 obj.Timestamp;

Sets or gets the int64 version of a date/time object. This is compatible with a range of operating system APIs.

string obj.Date;

Sets or gets the date part of the object. Typically this is in the form 'day/month/year' in the current locale of the operating system.

string obj.Time;

Sets or gets the time part of the object. Typically this is in the form 'hour:minute:second' in the current locale of the operating system.

string obj.DateTime;

Sets or gets both the date and time parts of the object as a string.

object CreateSurface(x, y[, pixel_type]);

Creates a bitmap in memory with the specified dimensions and bit depth or colour space.

Arguments:
  • x - The width in pixels.
  • y - The height in pixels.
  • [optional] pixel_type - The type of pixel.
    The default will be the same as the screen if you don't specify it at all. Otherwise you can use:
    • an integer: which specifies the bits per pixel.
      The exact colour space will be determined to be one of the System##BitColourSpace values.
    • a string: which specifies the exact colour space.
      Possible values are taken from the LColourSpace enum in Lgi but in string form, ie:
      • "CsIndex8" - Creates a 8 bit indexed image with a palette.
      • "CsRgb16" - An rgb image with 5 red, 6 green and 5 blue bits per pixel.
      • "CsRgb24" - An rgb image with 8 bits per component.
      • "CsRgba32" - An rgba image with 8 bits per component.
      Some special values are available for the supported system colour spaces:
      • "System15BitColourSpace" - The system supported 15 bits/pixel colour space.
      • "System16BitColourSpace" - The system supported 16 bits/pixel colour space.
      • "System24BitColourSpace" - The system supported 24 bits/pixel colour space.
      • "System32BitColourSpace" - The system supported 32 bits/pixel colour space.
      Not every system will support all colour spaces. Unsupported colour space images can't be placed on the screen directly. They have to be converted to a compatible colour space first.

Returns:
  • A memory bitmap object or NULL on failure.

Example:
c = CreateBitmap(320, 240);
 Print(c.Type(), ": ", c.x, ", ", c.y, ", ", c.Bits, ", ", c.ColourSpace, "\n");
Surface: 320, 240, 32, 1211639896
The colourspace '1211639896' decimal is '0x48382858' in hex. Each byte is a component description, the high nibble is the colour type (defined in Lgi as the GComponentType enum), and the lower nibble is the number of bits (and zero = 16 bits).

string ColourSpaceToString(intColourSpace);

Converts a LColourSpace integer to a string.

int StringToColourSpace(stringColourSpace);

Converts a string colour space representation to a LColourSpace.

Returns:
  • Non zero LColourSpace entry on success, or CsNone on error (value 0).

int obj.X;

The width of the image in pixels (read only).

int obj.Y;

The height of the image in pixels (read only).

int obj.Bits;

The number of bits per pixel (read only).

colour_space obj.ColourSpace;

The colour space of the image (read only). This is a integer value that maps to a LColourSpace enum.

list obj.Palette;

The palette of the image. Use array indexing to set or get the individual palette entries.

bool obj.Load(filename);

Loads an image from a file. Only filters that are compiled into the executable are supported.

Arguments:
  • filename - The path to the image file.

Returns:
  • TRUE if the file was loaded.

bool obj.Save(filename);

Soads an image to a file. Only filters that are compiled into the executable are supported.

Arguments:
  • filename - The path to the image file.

Returns:
  • TRUE if the file was saved.

view LoadDialog(parent_wnd, dialog_id);

Loads a dialog from a resource file.

Arguments:
  • parent_wnd - The handle of the parent window.
  • dialog_id - The id of the dialog from "resdefs.h".

Returns:
  • A view object.

string MessageDlg(parent_wnd, msg, title, btns);

Displays a model alert message.

Arguments:
  • parent_wnd - The handle of the parent window.
  • msg - A message above the editbox.
  • [optional] title - The title of the dialog.
  • [optional] btns - Integer mask of buttons:
    • MB_OK = 0
    • MB_OKCANCEL = 1
    • MB_ABORTRETRYIGNORE = 2
    • MB_YESNOCANCEL = 3
    • MB_YESNO = 4
    • MB_RETRYCANCEL = 5
    • MB_CANCELTRYCONTINUE = 6

Returns:
  • Integer representing the button pressed:
    • IDOK = 1
    • IDCANCEL = 2
    • IDABORT = 3
    • IDRETRY = 4
    • IDIGNORE = 5
    • IDYES = 6
    • IDNO = 7
    • IDCLOSE = 8
    • IDHELP = 9
    • IDTRYAGAIN = 10
    • IDCONTINUE = 11
    • IDTIMEOUT = 32000
-

string GetInputDlg(parent_wnd, initial_value, msg, title, is_password);

+

void GetInputDlg(parent_wnd, initial_value, msg, title, is_password, callback);

Asks the user for input using a dialog.

Arguments:
  • parent_wnd - The handle of the parent window.
  • initial_value - The initial value in the editbox field.
  • msg - A message above the editbox.
  • title - The title of the dialog.
  • is_password - True if you want the editbox content obsured by dot characters. +
  • callback - The function name to call with the dialog result code.

- Returns: -
    -
  • A string containing what the user entered. -
+ Returns: Nothing.
+
+
function MyCallback(str)
+{
+    Print("String entered is: '" + str + "'\n");
+}
+
+GetInputDlg(app, "SomeText", "Enter some text:", "InputTest", false, "MyCallback");

object GetViewById(parent, id);

Gets the child view by it's id.

Arguments:
  • parent - The parent window.
  • id - The id of the child.

Returns:
  • A view reference, or NULL if not found.

int obj.Handle;

The operating system handle for the view (read only).

int obj.Id;

The control ID for the view.

string obj.Name;

The textual name assocaited with the view.

int64 obj.Value;

The numerical value assocaited with the view.

bool obj.Enabled;

This sets or gets the enabled/disable state of the view.

colour obj.Foreground;

Gets or sets the CSS foreground colour for the view.

colour obj.Background;

Gets or sets the CSS background colour for the view.

string Execute(executable[, arguments]);

Executes a process and waits for it to finish.

Arguments:
  • executable - The path to the executable.
  • [optional] arguments - Arguments to pass to the executable.

Returns:
  • A string containing what the process wrote to stdout.

bool System(executable[, arguments]);

Executes a process and doesn't wait for it to finish.

Arguments:
  • executable - The path to the executable.
  • [optional] arguments - Arguments to pass to the executable.

Returns:
  • TRUE if the process was started.

string OsName();

Returns the current OS name:
  • Win32
  • Win64
  • MacOSX
  • Linux
  • Haiku

Example:
Print(OsName(), "\n");
Win64

list OsVersion();

Returns the current OS version as 3 integers in a list.

Returns: The OS version:
Windows XP {5, 1, 0}
Windows Vista {6, 0, 0}
Windows 7 {6, 1, 0}
Windows 8 {6, 2, 0}
Mac OS X 10.8.2 {10, 8, 2}
Ubuntu 13.04 {3, 8, 8} (i.e. the kernel version)

Example:
Print(OsVersion(), "\n");
{6, 1, 0}

string obj.Type;

Gets the type of the object as a string.

Returns:
  • A string.

Example:
c = New("list");
 Print(c.Type(), "\n");
List

int obj.Length;

Gets the length of an object. The meaning of that depends on the context of what type it is:
  • List or HashTable: number of objects in the container.
  • String: The number of bytes.
  • File: Reading the value gives the length of the file in bytes. Writing to the value sets the file's size.
  • Bitmap: Number of pixels.
  • View: Number of child views.

Returns:
  • A integer or NULL if not applicable.

Example:
c = "This is a string.";
 Print(c.Length, "\n");
 
 c = New("list");
 c.Add(123);
 c.Add(456);
 Print(c.Length, "\n");
 
17
 2
\ No newline at end of file diff --git a/include/lgi/common/Scripting.h b/include/lgi/common/Scripting.h --- a/include/lgi/common/Scripting.h +++ b/include/lgi/common/Scripting.h @@ -1,483 +1,488 @@ /// \file #ifndef _LGI_SCRIPTING_H_ #define _LGI_SCRIPTING_H_ #include "lgi/common/Variant.h" #include "lgi/common/List.h" class LScriptContext; class LScriptEnginePrivate; -class LVmDebuggerCallback; +class LVmCallback; class LVirtualMachine; class LScriptArguments : public LArray { friend class LScriptEngine; friend class LVirtualMachine; LVirtualMachine *Vm; LStream *Console; LVariant _Return; LVariant *PtrRet; public: static LStream NullConsole; LScriptArguments(LVirtualMachine *vm, LVariant *ret = NULL, LStream *console = NULL) { Vm = vm; if (ret) PtrRet = ret; else PtrRet = &_Return; if (console) Console = console; else Console = &NullConsole; } LVirtualMachine *GetVm() { return Vm; } LVariant *GetReturn() { return PtrRet; } LStream *GetConsole() { return Console; } bool Throw(const char *File, int Line, const char *Msg, ...); }; typedef bool (LScriptContext::*ScriptCmd)(LScriptArguments &Args); #define SCOPE_REGISTER 0 #define SCOPE_LOCAL 1 #define SCOPE_GLOBAL 2 #define SCOPE_OBJECT 3 #define SCOPE_RETURN 4 #define SCOPE_MAX 5 /// Execution status enum LExecutionStatus { + ScriptNotStarted, ScriptError, ScriptWarning, ScriptSuccess, }; /// Various type of methods enum LFuncType { /// No method type. NullFunc, /// This method is provided by the hosting application. HostFunc, /// This method is defined in the script itself. ScriptFunc, /// This method is defined in an external library. ExternFunc }; struct LFunc { LFuncType Type; LString Method; bool InUse; LFunc(const char *m = 0, LFuncType t = NullFunc) { Type = t; Method = m; InUse = false; // LStackTrace("%p alloc\n", this); } virtual ~LFunc() { // LAssert(!InUse); } virtual LExecutionStatus Call(LScriptContext *Ctx, LScriptArguments &Args) = 0; }; -struct GHostFunc : public LFunc +struct LHostFunc : public LFunc { LScriptContext *Context; LString Args; ScriptCmd Func; - GHostFunc(const GHostFunc &f) + LHostFunc(const LHostFunc &f) { Context = f.Context; Args = f.Args; Method = f.Method; Func = f.Func; } - GHostFunc(const char *method, const char *args, ScriptCmd proc) : LFunc(method, HostFunc) + LHostFunc(const char *method, const char *args, ScriptCmd proc) : LFunc(method, HostFunc) { Args = args; Func = proc; } LExecutionStatus Call(LScriptContext *Ctx, LScriptArguments &Args) override; }; struct LExternFunc : public LFunc { struct ExternType { int Ptr; bool Unsigned; bool Out; int ArrayLen; LVariantType Base; }; LAutoString Lib; ExternType ReturnType; LArray ArgType; LExecutionStatus Call(LScriptContext *Ctx, LScriptArguments &Args) override; }; class LFunctionInfo : public LRefCount { static int _Infos; int32 StartAddr = INVALID_ADDR; LString Name; // The reason why this is a pointer is because during the function compilation the frame // size is actually unknown. If the function calls itself then it won't know what // frame size to insert in the assembly (this is NULL). // In which case it has to insert a post compilation fix-up for the frame size. LAutoPtr FrameSize; // The number and names of the parameters to the function. LArray Params; public: static constexpr int32 INVALID_ADDR = -1; LFunctionInfo(const char *name) { if (name) Name = name; } ~LFunctionInfo() { } char *GetName() { return Name; } LArray &GetParams() { return Params; } bool ValidFrameSize() { return FrameSize.Get() != NULL; } uint16 GetFrameSize() { if (!FrameSize) { LAssert(!"Invalid frame size"); return 0; } return *FrameSize; } template bool SetFrameSize(T size) { if (size >= 0xffff) { LAssert(!"Invalid frame size."); return false; } return FrameSize.Reset(new uint16((uint16)size)); } bool ValidStartAddr() { return StartAddr != INVALID_ADDR; } int32 GetStartAddr() { LAssert(ValidStartAddr()); return StartAddr; } bool SetStartAddr(int32_t addr) { if (addr < 0) { LAssert(!"Invalid start address"); return false; } StartAddr = addr; return true; } LFunctionInfo &operator =(LFunctionInfo &f) { StartAddr = f.StartAddr; FrameSize = f.FrameSize; Name = f.Name; for (unsigned i=0; i { friend class LVirtualMachinePriv; LHashTbl,int> Lut; public: int Scope; int NullIndex; LCustomType *Obj; LVariables(int scope) { Scope = scope; NullIndex = -1; Obj = NULL; } LVariables(LCustomType *obj) { Scope = SCOPE_OBJECT; NullIndex = -1; Obj = obj; } int Var(const char *n, bool create = false) { if (Obj) { return Obj->IndexOf(n); } else { int p = Lut.Find(n); if (p) { return p - 1; } if (create) { int Len = (int)Length(); Lut.Add(n, Len + 1); Length(Len + 1); return Len; } } return -1; } }; /// A block of compile byte code class LCompiledCode { friend class LCompilerPriv; friend class LVirtualMachinePriv; friend class LCompiler; friend class LVmDebuggerWnd; /// The global variables LVariables Globals; /// The byte code of all the instructions LArray ByteCode; /// All the methods defined in the byte code and their arguments. LArray< LAutoRefPtr > Methods; /// All the externs defined in the code. LArray Externs; /// All the user types defined LHashTbl, class LCustomType*> Types; /// The original script details LString FileName; LString Source; /// The system context (all the system functions) LScriptContext *SysContext; /// Any user context (application functions) LScriptContext *UserContext; /// Debug info to map instruction address back to source line numbers LHashTbl, int> Debug; public: LCompiledCode(); LCompiledCode(LCompiledCode ©); ~LCompiledCode(); /// Size of the byte code size_t Length() { return ByteCode.Length(); } /// Assignment operator LCompiledCode &operator =(const LCompiledCode &c); /// Gets a method defined in the code LFunctionInfo *GetMethod(const char *Name, bool Create = false); /// Sets a global variable LVariant *Set(const char *Name, LVariant &v); /// Gets the definition of a struct or custom type LCustomType *GetType(char16 *Name) { return Types.Find(Name); } /// Gets the file name this code was compiled from const char *GetFileName() { return FileName; } /// Gets the source const char *GetSource() { return Source; } /// Gets the source line number associated with an address int ObjectToSourceAddress(size_t ObjAddr); /// Turns an object address into a FileName:LineNumber string. const char *AddrToSourceRef(size_t ObjAddr); /// Sets the file name this code was compiled from void SetSource(const char *file, const char *src) { if (file != FileName.Get()) FileName = file; Source = src; } }; /// New compiler/byte code/VM scripting engine class LScriptEngine { class LScriptEnginePrivate *d; public: - LScriptEngine(LViewI *parent, LScriptContext *UserContext, LVmDebuggerCallback *Callback); + LScriptEngine(LViewI *parent, LScriptContext *UserContext, LVmCallback *Callback); ~LScriptEngine(); LStream *GetConsole(); bool SetConsole(LStream *t); LCompiledCode *GetCurrentCode(); bool Compile( LAutoPtr &Obj, LScriptContext *UserContext, const char *Script, const char *FileName = NULL, LDom *Args = NULL); LExecutionStatus Run(LCompiledCode *Obj, LVariant *Ret = NULL, const char *TempPath = NULL); LExecutionStatus RunTemporary(LCompiledCode *Obj, char *Script, LVariant *Ret = NULL); bool EvaluateExpression(LVariant *Result, LDom *VariableSource, const char *Expression); bool CallMethod(LCompiledCode *Obj, const char *Method, LScriptArguments &Args); LScriptContext *GetSystemContext(); }; class LVirtualMachine; class LVmDebugger : public LDom { public: /// Set the VM ownership flag. virtual void OwnVm(bool Own) = 0; /// Makes the debugger the owner of the compiled code virtual void OwnCompiledCode(LAutoPtr Cc) = 0; /// Gets the code owned by the debugger virtual LCompiledCode *GetCode() = 0; /// Set the source and asm virtual void SetSource(const char *Mixed) = 0; /// Show UI and wait for user response virtual void Run() = 0; // Events /// Called to update the debugger UI to the new current execution position virtual void OnAddress(size_t Addr) = 0; /// Called when an error occurs executing the script virtual void OnError(const char *Msg) = 0; /// Called when execution starts or ends virtual void OnRun(bool Running) = 0; }; -class LVmDebuggerCallback : public LDom +class LVmCallback : public LDom { public: + /// Call a callback by name. + virtual bool CallCallback(LString CallbackName, LScriptArguments &Args) = 0; + /// Start a debugger instance to handle the execution in 'Vm' virtual LVmDebugger *AttachVm(LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) = 0; + /// Compile a new script virtual bool CompileScript(LAutoPtr &Output, const char *FileName, const char *Source) = 0; }; /// Debugger for vm script class LVmDebuggerWnd : public LWindow, public LVmDebugger { struct LScriptVmDebuggerPriv *d; void UpdateVariables(LList *Lst, LVariant *Arr, ssize_t Len, char Prefix); public: - LVmDebuggerWnd(LView *Parent, LVmDebuggerCallback *Callback, LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly); + LVmDebuggerWnd(LView *Parent, LVmCallback *Callback, LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly); ~LVmDebuggerWnd(); void OwnVm(bool Own); void OnAddress(size_t Addr); void OnError(const char *Msg); void OnRun(bool Running); void SetSource(const char *Mixed); int OnNotify(LViewI *Ctrl, LNotification n); int OnCommand(int Cmd, int Event, OsView Wnd); bool OnRequestClose(bool OsShuttingDown); LMessage::Param OnEvent(LMessage *Msg); void LoadFile(const char *File); LStream *GetLog(); void OwnCompiledCode(LAutoPtr Cc); LCompiledCode *GetCode(); void Run(); }; #endif diff --git a/include/lgi/common/Variant.h b/include/lgi/common/Variant.h --- a/include/lgi/common/Variant.h +++ b/include/lgi/common/Variant.h @@ -1,437 +1,437 @@ /** \file \author Matthew Allen \brief Variant class.\n Copyright (C), Matthew Allen */ #ifndef __LVariant_H__ #define __LVariant_H__ #include "lgi/common/Dom.h" #undef Bool #include "lgi/common/DateTime.h" #include "lgi/common/Containers.h" #include "lgi/common/HashTable.h" #include "lgi/common/LgiString.h" class LCompiledCode; #if !defined(_MSC_VER) && !defined(LINUX) && (defined(LGI_64BIT) || defined(MAC)) && !defined(HAIKU) #define LVARIANT_SIZET 1 #define LVARIANT_SSIZET 1 #endif /// The different types the varient can be. /// \sa LVariant::TypeToString to convert to string. enum LVariantType { // Main types /// Null type GV_NULL, /// 32-bit integer GV_INT32, /// 64-bit integer GV_INT64, /// true or false boolean. GV_BOOL, /// C++ double GV_DOUBLE, /// Null terminated string value GV_STRING, /// Block of binary data GV_BINARY, /// List of LVariant GV_LIST, /// Pointer to LDom object GV_DOM, /// DOM reference, ie. a variable in a DOM object GV_DOMREF, /// Untyped pointer GV_VOID_PTR, /// LDateTime class. GV_DATETIME, /// Hash table class, containing pointers to LVariants GV_HASHTABLE, // Scripting language operator GV_OPERATOR, // Custom scripting lang type GV_CUSTOM, // Wide string GV_WSTRING, // LSurface ptr GV_LSURFACE, /// Pointer to LView GV_GVIEW, /// Pointer to LMouse GV_LMOUSE, /// Pointer to LKey GV_LKEY, /// Pointer to LStream GV_STREAM, /// The maximum value for the variant type. /// (This is used by the scripting engine to refer to a LVariant itself) GV_MAX, }; /// Language operators enum LOperator { OpNull, OpAssign, OpPlus, OpUnaryPlus, OpMinus, OpUnaryMinus, OpMul, OpDiv, OpMod, OpLessThan, OpLessThanEqual, OpGreaterThan, OpGreaterThanEqual, OpEquals, OpNotEquals, OpPlusEquals, OpMinusEquals, OpMulEquals, OpDivEquals, OpPostInc, OpPostDec, OpPreInc, OpPreDec, OpAnd, OpOr, OpNot, }; class LgiClass LCustomType : public LDom { protected: struct CustomField : public LDom { ssize_t Offset; ssize_t Bytes; ssize_t ArrayLen; LVariantType Type; LString Name; LCustomType *Nested; ssize_t Sizeof(); bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); }; public: struct Method : public LDom { LString Name; LArray Params; size_t Address; int FrameSize; Method() { Address = -1; FrameSize = -1; } }; protected: // Global vars int Pack; size_t Size; LString Name; // Fields LArray Flds; LHashTbl, int> FldMap; // Methods LArray Methods; LHashTbl, Method*> MethodMap; // Private methods ssize_t PadSize(); public: LCustomType(const char *name, int pack = 1); LCustomType(const char16 *name, int pack = 1); ~LCustomType(); size_t Sizeof(); const char *GetName() { return Name; } ssize_t Members() { return Flds.Length(); } int AddressOf(const char *Field); int IndexOf(const char *Field); bool DefineField(const char *Name, LVariantType Type, int Bytes, int ArrayLen = 1); bool DefineField(const char *Name, LCustomType *Type, int ArrayLen = 1); Method *DefineMethod(const char *Name, LArray &Params, size_t Address); Method *GetMethod(const char *Name); // Field access. You can't use the LDom interface to get/set member variables because // there is no provision for the 'This' pointer. bool Get(int Index, LVariant &Out, uint8_t *This, int ArrayIndex = 0); bool Set(int Index, LVariant &In, uint8_t *This, int ArrayIndex = 0); // Dom access. However the DOM can be used to access information about the type itself. // Which doesn't need a 'This' pointer. bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args); }; /// A class that can be different types class LgiClass LVariant { public: typedef LHashTbl,LVariant*> LHash; /// The type of the variant LVariantType Type; /// The value of the variant union { /// Valid when Type == #GV_INT32 int Int; /// Valid when Type == #GV_BOOL bool Bool; /// Valid when Type == #GV_INT64 int64 Int64; /// Valid when Type == #GV_DOUBLE double Dbl; /// Valid when Type == #GV_STRING char *String; /// Valid when Type == #GV_WSTRING char16 *WString; /// Valid when Type == #GV_DOM LDom *Dom; /// Valid when Type is #GV_VOID_PTR, #GV_GVIEW, #GV_LMOUSE or #GV_LKEY void *Ptr; /// Valid when Type == #GV_BINARY struct _Binary { ssize_t Length; void *Data; } Binary; /// Valid when Type == #GV_LIST List *Lst; /// Valid when Type == #GV_HASHTABLE LHash *Hash; /// Valid when Type == #GV_DATETIME LDateTime *Date; /// Valid when Type == #GV_CUSTOM struct _Custom { LCustomType *Dom; uint8_t *Data; bool operator == (_Custom &c) { return Dom == c.Dom && Data == c.Data; } } Custom; /// Valid when Type == #GV_DOMREF struct _DomRef { /// The pointer to the dom object LDom *Dom; /// The name of the variable to set/get in the dom object char *Name; } DomRef; /// Valid when Type == #GV_OPERATOR LOperator Op; /// Valid when Type == #GV_LSURFACE struct { class LSurface *Ptr; bool Own; LSurface *Release() { auto p = Ptr; Ptr = NULL; Own = false; return p; } } Surface; /// Valid when Type == #GV_STREAM struct { class LStreamI *Ptr; bool Own; LStreamI *Release() { auto p = Ptr; Ptr = NULL; Own = false; return p; } } Stream; /// Valid when Type == #GV_GVIEW class LView *View; /// Valid when Type == #GV_LMOUSE class LMouse *Mouse; /// Valid when Type == #GV_LKEY class LKey *Key; } Value; /// Constructor to null LVariant(); /// Constructor for integers LVariant(int32_t i); LVariant(uint32_t i); LVariant(int64_t i); LVariant(uint64_t i); #if LVARIANT_SIZET LVariant(size_t i); #endif #if LVARIANT_SSIZET LVariant(ssize_t i); #endif /// Constructor for double LVariant(double i); /// Constructor for string LVariant(const char *s); /// Constructor for wide string LVariant(const char16 *s); /// Constructor for ptr LVariant(void *p); /// Constructor for DOM ptr LVariant(LDom *p); /// Constructor for DOM variable reference LVariant(LDom *p, char *name); /// Constructor for date LVariant(const LDateTime *d); /// Constructor for variant LVariant(LVariant const &v); /// Constructor for operator LVariant(LOperator Op); /// Destructor ~LVariant(); /// Assign bool value LVariant &operator =(bool i); /// Assign an integer value LVariant &operator =(int32_t i); LVariant &operator =(uint32_t i); LVariant &operator =(int64_t i); LVariant &operator =(uint64_t i); #if LVARIANT_SIZET LVariant &operator =(size_t i); #endif #if LVARIANT_SSIZET LVariant &operator =(ssize_t i); #endif /// Assign double value LVariant &operator =(double i); /// Assign string value (makes a copy) LVariant &operator =(const char *s); /// Assign a wide string value (makes a copy) LVariant &operator =(const char16 *s); /// Assign another variant value LVariant &operator =(LVariant const &i); /// Assign value to a void ptr LVariant &operator =(void *p); /// Assign value to DOM ptr LVariant &operator =(LDom *p); /// Assign value to be a date/time LVariant &operator =(const LDateTime *d); LVariant &operator =(class LView *p); LVariant &operator =(class LMouse *p); LVariant &operator =(class LKey *k); LVariant &operator =(class LStream *s); bool operator ==(LVariant &v); bool operator !=(LVariant &v) { return !(*this == v); } /// Sets the value to a DOM variable reference bool SetDomRef(LDom *obj, char *name); /// Sets the value to a copy of block of binary data bool SetBinary(ssize_t Len, void *Data, bool Own = false); /// Sets the value to a copy of the list - bool SetList(List *Lst = NULL); + List *SetList(List *Lst = NULL); /// Sets the value to a hashtable bool SetHashTable(LHash *Table = NULL, bool Copy = true); /// Set the value to a surface bool SetSurface(class LSurface *Ptr, bool Own); /// Set the value to a stream bool SetStream(class LStreamI *Ptr, bool Own); /// Returns the string if valid (will convert a GV_WSTRING to utf) char *Str(); /// Returns the value as an LString LString LStr(); /// Returns a wide string if valid (will convert a GV_STRING to wide) char16 *WStr(); /// Returns the string, releasing ownership of the memory to caller and /// changing this LVariant to GV_NULL. char *ReleaseStr(); /// Returns the wide string, releasing ownership of the memory to caller and /// changing this LVariant to GV_NULL. char16 *ReleaseWStr(); /// Sets the variant to a heap string and takes ownership of it bool OwnStr(char *s); /// Sets the variant to a wide heap string and takes ownership of it bool OwnStr(char16 *s); /// Sets the variant to NULL void Empty(); /// Returns the byte length of the data int64 Length(); /// True if currently a int bool IsInt(); /// True if currently a bool bool IsBool(); /// True if currently a double bool IsDouble(); /// True if currently a string bool IsString(); /// True if currently a binary block bool IsBinary(); /// True if currently null bool IsNull(); /// Changes the variant's type, maintaining the value where possible. If /// no conversion is available then nothing happens. LVariant &Cast(LVariantType NewType); /// Casts the value to int, from whatever source type. The /// LVariant type does not change after calling this. int32 CastInt32() const; /// Casts the value to a 64 bit int, from whatever source type. The /// LVariant type does not change after calling this. int64 CastInt64() const; /// Casts the value to double, from whatever source type. The /// LVariant type does not change after calling this. double CastDouble() const; /// Cast to a string from whatever source type, the LVariant will /// take the type GV_STRING after calling this. This is because /// returning a static string is not thread safe. char *CastString(); /// Casts to a DOM ptr LDom *CastDom() const; /// Casts to a boolean. You probably DON'T want to use this function. The /// behaviour for strings -> bool is such that if the string is value it /// always evaluates to true, and false if it's not a valid string. Commonly /// what you want is to evaluate whether the string is zero or non-zero in /// which cast you should use "CastInt32() != 0" instead. bool CastBool() const; /// Returns the pointer if available. void *CastVoidPtr() const; /// Returns a LView LView *CastView() const { return Type == GV_GVIEW ? Value.View : NULL; } /// List insert bool Add(LVariant *v, int Where = -1); /// Converts the varient type to a string static const char *TypeToString(LVariantType t); /// Converts an operator to a string static const char *OperatorToString(LOperator op); /// Converts the value to a string description include type. LString ToString(); }; #endif diff --git a/src/common/Coding/Instructions.h b/src/common/Coding/Instructions.h --- a/src/common/Coding/Instructions.h +++ b/src/common/Coding/Instructions.h @@ -1,2037 +1,2037 @@ /* This file is included in both the LVirtualMachinePriv::Execute and LVirtualMachinePriv::Decompile That way the "parsing" of instructions is the same. During decompile the define VM_DECOMP is active. During execution the define VM_EXECUTE is active. */ #ifdef VM_EXECUTE #define Resolve() &Scope[c.r->Scope][c.r->Index]; c.r++ #define GResolveRef(nm) LVariant *nm = #else #define Resolve() c.r++ #define GResolveRef(nm) // LVarRef * #endif default: { #if VM_DECOMP if (Log) Log->Print("\t%p Unknown instruction %i (0x%x)\n", CurrentScriptAddress - 1, c.u8[-1], c.u8[-1]); #endif OnException(_FL, CurrentScriptAddress, "Unknown instruction"); SetScriptError; break; } case INop: { #if VM_DECOMP if (Log) Log->Print("%p Nop\n", CurrentScriptAddress - 1); #endif break; } case ICast: { #if VM_DECOMP if (Log) Log->Print("%p Cast %s", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(Var) Resolve(); uint8_t Type = *c.u8++; #if VM_DECOMP if (Log) Log->Print(" to %s\n", LVariant::TypeToString((LVariantType)Type)); #endif #if VM_EXECUTE switch (Type) { case GV_INT32: { *Var = Var->CastInt32(); break; } case GV_STRING: { *Var = Var->CastString(); break; } case GV_DOM: { *Var = Var->CastDom(); break; } case GV_DOUBLE: { *Var = Var->CastDouble(); break; } case GV_INT64: { *Var = Var->CastInt32(); break; } case GV_BOOL: { *Var = Var->CastBool(); break; } default: { LString s; s.Printf("%s ICast warning: unknown type %i/%s\n", Code->AddrToSourceRef(CurrentScriptAddress), Var->Type, LVariant::TypeToString(Var->Type)); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IAssign: { #if VM_DECOMP if (Log) Log->Print("%p Assign %s <- %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE CheckParam(Dst != Src); *Dst = *Src; #endif break; } case IJump: { #if VM_DECOMP if (Log) Log->Print("%p Jump by %i (to 0x%x)\n", CurrentScriptAddress - 1, c.i32[0], CurrentScriptAddress + 4 + c.i32[0]); #endif #ifdef VM_EXECUTE int32 Jmp = * #endif c.i32++; #ifdef VM_EXECUTE CheckParam(Jmp != 0); c.u8 += Jmp; #endif break; } case IJumpZero: { #if VM_DECOMP if (Log) Log->Print("%p JumpZ(%s) by 0x%x\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.i32[1]); #endif GResolveRef(Exp) Resolve(); #ifdef VM_EXECUTE int32 Jmp = * #endif c.i32++; #ifdef VM_EXECUTE CheckParam(Jmp != 0); if (!Exp->CastInt32()) c.u8 += Jmp; #endif break; } // case IUnaryPlus: case IUnaryMinus: { #if VM_DECOMP if (Log) Log->Print("%p UnaryMinus %s\n", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(Var) Resolve(); #ifdef VM_EXECUTE switch (Var->Type) { case GV_DOUBLE: *Var = -Var->CastDouble(); break; case GV_INT64: *Var = -Var->CastInt64(); break; default: *Var = -Var->CastInt32(); break; } #endif break; } case IPlus: case IPlusEquals: { #if VM_DECOMP if (Log) Log->Print("%p Plus %s += %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE if (Dst->Str()) { size_t dlen = strlen(Dst->Str()); char *ss; LVariant SrcTmp; switch (Src->Type) { case GV_NULL: ss = (char*)"(null)"; break; case GV_STRING: ss = Src->Str(); break; default: SrcTmp = *Src; ss = SrcTmp.CastString(); break; } if (ss) { size_t slen = strlen(ss); char *s = new char[slen + dlen + 1]; if (s) { memcpy(s, Dst->Value.String, dlen); memcpy(s + dlen, ss, slen); s[dlen + slen] = 0; DeleteArray(Dst->Value.String); Dst->Value.String = s; } } } else switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() + Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() + Src->CastInt64(); break; default: *Dst = Dst->CastInt32() + Src->CastInt32(); break; } #endif break; } case IMinus: case IMinusEquals: { #if VM_DECOMP if (Log) Log->Print("%p Minus %s -= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() - Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() - Src->CastInt64(); break; default: *Dst = Dst->CastInt32() - Src->CastInt32(); break; } #endif break; } case IMul: case IMulEquals: { #if VM_DECOMP if (Log) Log->Print("%p Mul %s *= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() * Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() * Src->CastInt64(); break; default: *Dst = Dst->CastInt32() * Src->CastInt32(); break; } #endif break; } case IDiv: case IDivEquals: { #if VM_DECOMP if (Log) Log->Print("%p Div %s /= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() / Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() / Src->CastInt64(); break; default: *Dst = Dst->CastInt32() / Src->CastInt32(); break; } #endif break; } case IMod: { #if VM_DECOMP if (Log) Log->Print("%p Mod %s %%= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = fmod(Dst->CastDouble(), Src->CastDouble()); break; case GV_INT64: *Dst = Dst->CastInt64() % Src->CastInt64(); break; default: *Dst = Dst->CastInt32() % Src->CastInt32(); break; } #endif break; } case IPostInc: case IPreInc: { #if VM_DECOMP if (Log) Log->Print("%p PostInc %s\n", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(v) Resolve(); #ifdef VM_EXECUTE switch (v->Type) { case GV_DOUBLE: *v = v->Value.Dbl + 1; break; case GV_INT64: *v = v->Value.Int64 + 1; break; default: *v = v->CastInt32() + 1; break; } #endif break; } case IPostDec: case IPreDec: { #if VM_DECOMP if (Log) Log->Print("%p PostDec %sn", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(v) Resolve(); #ifdef VM_EXECUTE switch (v->Type) { case GV_DOUBLE: *v = v->Value.Dbl - 1; break; case GV_INT64: *v = v->Value.Int64 - 1; break; default: *v = v->CastInt32() - 1; break; } #endif break; } case IEquals: { #if VM_DECOMP if (Log) Log->Print("%p %s == %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) == 0; #endif break; } case INotEquals: { #if VM_DECOMP if (Log) Log->Print( "%p %s != %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) != 0; #endif break; } case ILessThan: { #if VM_DECOMP if (Log) Log->Print("%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) < 0; #endif break; } case ILessThanEqual: { #if VM_DECOMP if (Log) Log->Print( "%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) <= 0; #endif break; } case IGreaterThan: { #if VM_DECOMP if (Log) Log->Print("%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) > 0; #endif break; } case IGreaterThanEqual: { #if VM_DECOMP if (Log) Log->Print("%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) >= 0; #endif break; } case ICallMethod: { LFunc *Meth = *c.fn++; if (!Meth) { Log->Print( "%s ICallMethod error: No method struct.\n", Code->AddrToSourceRef(CurrentScriptAddress - sizeof(Meth))); SetScriptError; break; } #ifdef VM_DECOMP if (Log) { Log->Print("%p Call: %s = %s(", CurrentScriptAddress - sizeof(Meth) - 1, c.r[0].GetStr(), Meth->Method.Get()); } #endif GResolveRef(Ret) Resolve(); uint16 Args = *c.u16++; #ifdef VM_EXECUTE LScriptArguments Arg(Vm, Ret); #endif for (int i=0; iPrint("%s%s", i?", ":"", c.r[0].GetStr()); #endif #if VM_EXECUTE Arg[i] = Resolve(); CheckParam(Arg[i] != NULL); #else c.r++; #endif } #if VM_DECOMP if (Log) Log->Print(")\n"); #endif #if VM_EXECUTE - GHostFunc *Hf = dynamic_cast(Meth); + LHostFunc *Hf = dynamic_cast(Meth); if (Hf) { if (!(Hf->Context->*(Hf->Func))(Arg)) { if (Log) Log->Print( "%s ICallMethod error: Method '%s' failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), Meth->Method.Get()); SetScriptError; } } else { // Fixme if (!Meth->Call(NULL, Arg)) { if (Log) Log->Print( "%s ICallMethod error: Method '%s' failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), Meth->Method.Get()); SetScriptError; } } #endif break; } case ICallScript: { int32 FuncAddr = *c.i32++; if (FuncAddr < 0 || (uint32_t)FuncAddr >= Code->ByteCode.Length()) { Log->Print( "%s ICallScript error: Script function call invalid addr '%p'.\n", Code->AddrToSourceRef(CurrentScriptAddress - sizeof(FuncAddr)), FuncAddr); SetScriptError; break; } uint16 Frame = *c.u16++; #if VM_DECOMP if (Log) Log->Print("%p CallScript: %s = %p(frame=%i)(", CurrentScriptAddress - 5, c.r[0].GetStr(), FuncAddr, Frame); #endif #ifdef VM_EXECUTE // Set up stack for function call int CurFrameSize = Frames.Last().CurrentFrameSize; StackFrame &Sf = Frames.New(); Sf.CurrentFrameSize = Frame; Sf.PrevFrameStart = Locals.Length() ? Scope[1] - &Locals[0] : 0; Sf.ReturnValue = *c.r++; if (Sf.ReturnValue.Scope == SCOPE_LOCAL) Sf.ReturnValue.Index -= CurFrameSize; uint16 Args = *c.u16++; // Increase the local stack size size_t LocalsBase = Locals.Length(); size_t LocalsPos = Scope[SCOPE_LOCAL] - Locals.AddressOf(); Locals.SetFixedLength(false); Locals.Length(LocalsBase + Frame); Locals.SetFixedLength(); Scope[SCOPE_LOCAL] = Locals.AddressOf(LocalsPos); // Put the arguments of the function call into the local array LArray Arg; #else GResolveRef(Ret) Resolve(); int Args = *c.u16++; #endif for (int i=0; iPrint("%s%s", i?",":"", c.r[0].GetStr()); #endif #if VM_EXECUTE Locals[LocalsBase+i] = *Resolve(); #else c.r++; #endif } #if VM_EXECUTE // Set IP to start of function Sf.ReturnIp = CurrentScriptAddress; c.u8 = Base + FuncAddr; Scope[SCOPE_LOCAL] = Locals.AddressOf(LocalsBase); // This can evaluation to NULL when there is NO locals. #endif #if VM_DECOMP if (Log) Log->Print(")\n"); #endif break; } case IRet: { #if VM_DECOMP if (Log) Log->Print("%p Ret %s\n", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(ReturnValue) Resolve(); #ifdef VM_EXECUTE if (Frames.Length() > 0) { StackFrame Sf = Frames[Frames.Length()-1]; LVarRef &Ret = Sf.ReturnValue; LVariant *RetVar = &Scope[Ret.Scope][Ret.Index]; // LgiTrace("IRet to %i:%i\n", Ret.Scope, Ret.Index); if (Ret.Scope == SCOPE_LOCAL) LAssert(Locals.PtrCheck(RetVar)); *RetVar = *ReturnValue; CheckParam(RetVar->Type == ReturnValue->Type); Frames.Length(Frames.Length()-1); Locals.SetFixedLength(false); if (Locals.Length() >= Sf.CurrentFrameSize) { ssize_t Base = Locals.Length() - Sf.CurrentFrameSize; if (ArgsOutput) { if (Frames.Length() == 0) { for (unsigned i=0; iLength(); i++) { *(*ArgsOutput)[i] = Locals[Base+i]; } } } // LgiTrace("%s:%i Locals %i -> %i\n", _FL, Locals.Length(), Base); Locals.Length(Base); Scope[SCOPE_LOCAL] = &Locals[Sf.PrevFrameStart]; } else { // LgiTrace("%s:%i - Locals %i -> %i\n", _FL, Locals.Length(), 0); Locals.Length(0); Scope[SCOPE_LOCAL] = NULL; } Locals.SetFixedLength(); c.u8 = Base + Sf.ReturnIp; } else { ExitScriptExecution; } #endif break; } case IArrayGet: { #if VM_DECOMP if (Log) Log->Print( "%p ArrayGet %s = %s[%s]\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Var) Resolve(); GResolveRef(Idx) Resolve(); #ifdef VM_EXECUTE switch (Var->Type) { case GV_LIST: { CheckParam(Var->Value.Lst); LVariant *t = Var->Value.Lst->ItemAt(Idx->CastInt32()); if (t) { if (Var == Dst) { if (Var->Value.Lst->Delete(t)) { *Var = *t; DeleteObj(t); } else CheckParam(!"List delete failed."); } else *Dst = *t; } else Dst->Empty(); break; } case GV_HASHTABLE: { CheckParam(Var->Value.Hash); LVariant *t = (LVariant*)Var->Value.Hash->Find(Idx->CastString()); if (t) *Dst = *t; else Dst->Empty(); break; } case GV_CUSTOM: { LCustomType *T = Var->Value.Custom.Dom; size_t Sz = T->Sizeof(); int Index = Idx->CastInt32(); Dst->Type = GV_CUSTOM; Dst->Value.Custom.Dom = T; Dst->Value.Custom.Data = Var->Value.Custom.Data + (Sz * Index); break; } case GV_STRING: { auto c = Var->Str(); auto i = Idx->CastInt64(); if (!c || i < 0) break; LUtf8Ptr p(c); uint32_t ch; do { ch = p; if (i-- == 0) { *Dst = ch; break; } p++; } while (ch); break; } default: { LString s; s.Printf("%s IArrayGet warning: Can't array deref variant type %i\n", Code->AddrToSourceRef(CurrentScriptAddress), Var->Type); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IArraySet: { #if VM_DECOMP if (Log) Log->Print( "%p ArraySet %s[%s] = %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr()); #endif GResolveRef(Var) Resolve(); GResolveRef(Idx) Resolve(); GResolveRef(Val) Resolve(); #ifdef VM_EXECUTE switch (Var->Type) { case GV_LIST: { CheckParam(Var->Value.Lst); (*Var->Value.Lst).Insert(new LVariant(*Val), Idx->CastInt32()); break; } case GV_HASHTABLE: { CheckParam(Var->Value.Hash); LVariant *Old = (LVariant*)Var->Value.Hash->Find(Idx->CastString()); DeleteObj(Old); Var->Value.Hash->Add(Idx->CastString(), new LVariant(*Val)); break; } default: { LString s; s.Printf("%s IArraySet warning: Can't dereference type '%s'\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Var->Type)); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IAnd: { #if VM_DECOMP if (Log) Log->Print("%p %s && %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = (Dst->CastInt32() != 0) && (Src->CastInt32() != 0); #endif break; } case IOr: { #if VM_DECOMP if (Log) Log->Print("%p %s || %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = (Dst->CastInt32() != 0) || (Src->CastInt32() != 0); #endif break; } case INot: { #if VM_DECOMP if (Log) Log->Print("%p %s = !%s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[0].GetStr()); #endif GResolveRef(Dst) Resolve(); #ifdef VM_EXECUTE *Dst = !Dst->CastBool(); #endif break; } case IDomGet: { #if VM_DECOMP if (Log) Log->Print("%p %s = %s->DomGet(%s, %s)\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr(), c.r[3].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Dom) Resolve(); GResolveRef(Name) Resolve(); GResolveRef(Arr) Resolve(); #ifdef VM_EXECUTE // Return "NULL" in Dst on error if (Dst != Dom) Dst->Empty(); switch (Dom->Type) { case GV_DOM: case GV_STREAM: case GV_LSURFACE: { auto *dom = Dom->CastDom(); CheckParam(dom != NULL); char *sName = Name->Str(); CheckParam(sName); bool Ret = dom->GetVariant(sName, *Dst, CastArrayIndex(Arr)); if (!Ret) { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; } break; } case GV_DATETIME: { CheckParam(Dom->Value.Date != NULL); char *sName = Name->Str(); CheckParam(sName); bool Ret = Dom->Value.Date->GetVariant(sName, *Dst, CastArrayIndex(Arr)); if (!Ret) { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; } break; } case GV_CUSTOM: { LCustomType *Type = Dom->Value.Custom.Dom; if (Type) { int Fld; if (Name->Type == GV_INT32) Fld = Name->Value.Int; else Fld = Type->IndexOf(Name->Str()); int Index = Arr ? Arr->CastInt32() : 0; Type->Get(Fld, *Dst, Dom->Value.Custom.Data, Index); } break; } case GV_LIST: { CheckParam(Dom->Value.Lst); char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); if (p == ObjLength) (*Dst) = (int)Dom->Value.Lst->Length(); break; } case GV_HASHTABLE: { CheckParam(Dom->Value.Hash); char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); if (p == ObjLength) (*Dst) = (int)Dom->Value.Hash->Length(); break; } case GV_BINARY: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); if (p == ObjLength) (*Dst) = Dom->Value.Binary.Length; break; } case GV_INT32: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); switch (p) { case TypeString: { char s[32]; sprintf_s(s, sizeof(s), "%i", Dom->Value.Int); *Dst = s; break; } case TypeDouble: { *Dst = (double)Dom->Value.Int; break; } default: { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected int32 member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } break; } case GV_INT64: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); switch (p) { case TypeString: { char s[32]; sprintf_s(s, sizeof(s), LPrintfInt64, Dom->Value.Int64); *Dst = s; break; } case TypeDouble: { *Dst = (double)Dom->Value.Int64; break; } default: { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected int64 member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } break; } case GV_DOUBLE: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); switch (p) { case TypeString: { char s[32]; sprintf_s(s, sizeof(s), "%g", Dom->Value.Dbl); *Dst = s; break; } case TypeInt: { *Dst = (int64)Dom->Value.Dbl; break; } default: { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected double member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } break; } case GV_STRING: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); switch (p) { case ObjLength: { (*Dst) = (int)strlen(Dom->Str()); break; } case TypeInt: { (*Dst) = Dom->CastInt32(); break; } case TypeDouble: { (*Dst) = Dom->CastDouble(); break; } default: { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected string member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } break; } case GV_NULL: { LString s; s.Printf("%s IDomGet warning: Can't deref NULL object.\n", Code->AddrToSourceRef(CurrentScriptAddress)); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } default: { LString s; s.Printf("%s IDomGet warning: Unexpected type %s (Src=%s:%i IP=0x%x).\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), _FL, CurrentScriptAddress); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IDomSet: { #if VM_DECOMP if (Log) Log->Print("%p %s->DomSet(%s, %s) = %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr(), c.r[3].GetStr()); #endif GResolveRef(Dom) Resolve(); GResolveRef(Name) Resolve(); GResolveRef(Arr) Resolve(); GResolveRef(Value) Resolve(); #ifdef VM_EXECUTE char *sName = Name->Str(); if (!sName) { if (Log) Log->Print("%s IDomSet error: No name string.\n", Code->AddrToSourceRef(CurrentScriptAddress)); SetScriptError; break; } switch (Dom->Type) { case GV_DOM: // case GV_GFILE: case GV_STREAM: case GV_LSURFACE: { auto *dom = Dom->CastDom(); CheckParam(dom != NULL); bool Ret = dom->SetVariant(sName, *Value, CastArrayIndex(Arr)); if (!Ret) { if (Log) Log->Print("%s IDomSet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); Status = ScriptWarning; } break; } case GV_DATETIME: { CheckParam(Dom->Value.Date != NULL); bool Ret = Dom->Value.Date->SetVariant(sName, *Value, CastArrayIndex(Arr)); if (!Ret) { if (Log) Log->Print("%s IDomSet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); Status = ScriptWarning; } break; } case GV_CUSTOM: { LCustomType *Type = Dom->Value.Custom.Dom; if (Type) { int Fld; if (IsDigit(*sName)) Fld = atoi(sName); else Fld = Type->IndexOf(sName); int Index = Arr ? Arr->CastInt32() : 0; if (!Type->Set(Fld, *Value, Dom->Value.Custom.Data, Index) && Log) { Log->Print("%s IDomSet warning: Couldn't set '%s' on custom type.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); } } break; } case GV_STRING: { LDomProperty p = LStringToDomProp(sName); switch (p) { case ObjLength: { char *s; int DLen = Value->CastInt32(); if (DLen && (s = new char[DLen+1])) { size_t SLen = Dom->Str() ? strlen(Dom->Str()) : 0; if (SLen) memcpy(s, Dom->Str(), SLen); memset(s+SLen, ' ', DLen-SLen); s[DLen] = 0; DeleteArray(Dom->Value.String); Dom->Value.String = s; } else Dom->Empty(); break; } case TypeInt: { *Dom = Value->CastInt32(); Dom->Str(); break; } case TypeDouble: { *Dom = Value->CastDouble(); Dom->Str(); break; } default: { if (Log) Log->Print("%s IDomSet warning: Unexpected string member %s.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; break; } } break; } default: { if (Log) Log->Print("%s IDomSet warning: Unexpected type %s.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type)); Status = ScriptWarning; break; } } #endif break; } case IDomCall: { #if VM_DECOMP if (Log) Log->Print("%p %s = %s->DomCall(%s, ", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr()); #else LVarRef DstRef = *c.r; #endif GResolveRef(Dst) Resolve(); GResolveRef(Dom) Resolve(); GResolveRef(Name) Resolve(); #ifdef VM_EXECUTE GResolveRef(Args) Resolve(); int ArgCount = Args->CastInt32(); char *sName = Name->Str(); CheckParam(sName) if (Dom->Type == GV_CUSTOM) { #define DEBUG_CUSTOM_METHOD_CALL 1 LCustomType *t = Dom->Value.Custom.Dom; CheckParam(t); LCustomType::Method *m = t->GetMethod(sName); CheckParam(m); CheckParam(m->Params.Length() == ArgCount); // Set up new stack frame... StackFrame &Sf = Frames.New(); Sf.CurrentFrameSize = m->FrameSize; Sf.PrevFrameStart = Locals.Length() ? Scope[1] - &Locals[0] : 0; Sf.ReturnValue = DstRef; // Increase the local stack size AddLocalSize(m->FrameSize + 1); #if DEBUG_CUSTOM_METHOD_CALL LgiTrace("CustomType.Call(%s) Args=%i, Frame=%i, Addr=%i, LocalsBase=%i ", sName, ArgCount, m->FrameSize, m->Address, LocalsBase); #endif size_t i = LocalsBase; Locals[i++] = *Dom; // this pointer... #if DEBUG_CUSTOM_METHOD_CALL LString s = Locals[i-1].ToString(); LgiTrace("This=%s, ", s.Get()); #endif size_t end = i + ArgCount; while (i < end) { Locals[i++] = *Resolve(); #if DEBUG_CUSTOM_METHOD_CALL s = Locals[i-1].ToString(); LgiTrace("[%i]=%s, ", i-1, s.Get()); #endif } // Now adjust the local stack to point to the locals for the function Scope[1] = Locals.Length() ? &Locals[LocalsBase] : NULL; // Set IP to start of function Sf.ReturnIp = CurrentScriptAddress; c.u8 = Base + m->Address; #if DEBUG_CUSTOM_METHOD_CALL LgiTrace("\n"); #endif break; } LArray Arg; Arg.Length(ArgCount); for (int i=0; iType); } else switch (Dom->Type) { case GV_DOM: case GV_STREAM: case GV_LSURFACE: { auto *dom = Dom->CastDom(); CheckParam(dom); bool Ret = dom->CallMethod(sName, Dst, Arg); if (!Ret) { Dst->Empty(); if (Log) Log->Print("%s IDomCall warning: %s(...) failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; } break; } case GV_DATETIME: { CheckParam(Dom->Value.Date); bool Ret = Dom->Value.Date->CallMethod(sName, Dst, Arg); if (!Ret) { Dst->Empty(); if (Log) Log->Print("%s IDomCall warning: %s(...) failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; } break; } case GV_LIST: { CheckParam(Dom->Value.Lst); switch (p) { case ObjLength: { *Dst = (int64)Dom->Value.Lst->Length(); break; } case ContainerAdd: { if (Arg.Length() > 0 && Arg[0]) { int Index = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; LVariant *v = new LVariant; *v = *Arg[0]; Dom->Value.Lst->Insert(v, Index); } break; } case ContainerDelete: { for (unsigned i=0; iCastInt32(); LVariant *Elem = Dom->Value.Lst->ItemAt(n); if (Elem) { Dom->Value.Lst->Delete(Elem); DeleteObj(Elem); } } } break; } case ContainerHasKey: { if (Arg.Length() > 0 && Arg[0]) { int Index = Arg[0]->CastInt32(); *Dst = (bool) (Index >= 0 && Index < (int)Dom->Value.Lst->Length()); } else { *Dst = false; } break; } case ContainerSort: { LVariant *Param = Arg.Length() > 0 ? Arg[0] : NULL; Dom->Value.Lst->Sort(LVariantCmp, (NativeInt)Param); break; } default: { Dst->Empty(); if (Log) Log->Print( "%s IDomCall warning: Unexpected list member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; break; } } break; } case GV_HASHTABLE: { CheckParam(Dom->Value.Hash); switch (p) { case ObjLength: { *Dst = Dom->Value.Hash->Length(); break; } case ContainerAdd: { if (Arg.Length() == 2 && Arg[0] && Arg[1]) { char *Key = Arg[1]->Str(); if (Key) { LVariant *v = new LVariant; *v = *Arg[0]; Dom->Value.Hash->Add(Key, v); } } break; } case ContainerDelete: { if (Arg.Length() == 1 && Arg[0]) { char *Key = Arg[0]->Str(); if (Key) { LVariant *v = (LVariant*) Dom->Value.Hash->Find(Key); if (v) { Dom->Value.Hash->Delete(Key); delete v; } } } break; } case ContainerHasKey: { if (Arg.Length() > 0 && Arg[0]) { char *Key = Arg[0]->Str(); *Dst = (bool) (Dom->Value.Hash->Find(Key) != NULL); } else { *Dst = false; } break; } default: { Dst->Empty(); if (Log) Log->Print("%s IDomCall warning: Unexpected hashtable member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; break; } } break; } case GV_BINARY: { switch (p) { default: break; case ObjLength: *Dst = Dom->Value.Binary.Length; break; } break; } case GV_STRING: { if (Arg.Length() > 0 && !Arg[0]) { Dst->Empty(); break; } switch (p) { case ObjLength: { char *s = Dom->Str(); *Dst = (int) (s ? strlen(s) : 0); break; } case StrJoin: { switch (Arg[0]->Type) { case GV_LIST: { LStringPipe p(256); List *Lst = Arg[0]->Value.Lst; const char *Sep = Dom->CastString(); auto It = Lst->begin(); LVariant *v = *It; if (v) { LVariant Tmp = *v; p.Print("%s", Tmp.CastString()); while ((v = *(++It))) { Tmp = *v; p.Print("%s%s", Sep, Tmp.CastString()); } } Dst->OwnStr(p.NewStr()); break; } default: { *Dst = *Arg[0]; Dst->CastString(); break; } } break; } case StrSplit: { const char *Sep = Arg[0]->Str(); if (!Sep) { Dst->Empty(); break; } LVariant Tmp; if (Dst == Dom) { Tmp = *Dom; Dom = &Tmp; } Dst->SetList(); size_t SepLen = strlen(Sep); int MaxSplit = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; const char *c = Dom->CastString(); while (c && *c) { if (MaxSplit > 0 && (int)Dst->Value.Lst->Length() >= MaxSplit) break; const char *next = strstr(c, Sep); if (!next) break; LVariant *v = new LVariant; v->OwnStr(NewStr(c, next - c)); Dst->Value.Lst->Insert(v); c = next + SepLen; } if (c && *c) { LVariant *v = new LVariant; v->OwnStr(NewStr(c)); Dst->Value.Lst->Insert(v); } break; } case StrSplitDelimit: { const char *Sep = Arg[0]->Str(); if (!Sep) { Dst->Empty(); break; } LVariant Tmp; if (Dst == Dom) { Tmp = *Dom; Dom = &Tmp; } Dst->SetList(); int MaxSplit = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; const char *c = Dom->CastString(); while (c && *c) { if (MaxSplit > 0 && (int)Dst->Value.Lst->Length() >= MaxSplit) break; const char *next = c; while (*next && !strchr(Sep, *next)) next++; LVariant *v = new LVariant; v->OwnStr(NewStr(c, next - c)); Dst->Add(v); for (c = next; *c && strchr(Sep, *c); c++) ; } if (c && *c) Dst->Add(new LVariant(c)); break; } case StrFind: { const char *s = Dom->Str(); if (!s) { *Dst = -1; break; } ssize_t sLen = Strlen(s); auto sub = Arg[0]->Str(); auto start = Arg.Length() > 1 ? Arg[1]->CastInt32() : 0; auto end = Arg.Length() > 2 ? Arg[2]->CastInt32() : -1; if (start >= sLen) { *Dst = -1; break; } char *sStart = (char*)s + start; char *pos; if (end >= 0) pos = Strnstr(sStart, sub, end); else pos = Strstr(sStart, sub); if (pos) *Dst = (int64) (pos - s); else *Dst = -1; break; } case StrRfind: { const char *s = Dom->Str(); if (!s) { *Dst = -1; break; } ssize_t sLen = strlen(s); auto sub = Arg[0]->Str(); auto start_idx = Arg.Length() > 1 ? Arg[1]->CastInt32() : 0; auto end_idx = Arg.Length() > 2 ? Arg[2]->CastInt32() : -1; if (start_idx >= sLen) { *Dst = -1; break; } auto sublen = Strlen(sub); auto cur = s + start_idx; auto end = end_idx >= 0 ? cur + end_idx : NULL; const char *pos = NULL; while (true) { cur = (end) ? Strnstr(cur, sub, end - cur) : Strstr(cur, sub); if (cur) { pos = cur; cur += sublen; } else break; } if (pos) *Dst = (int64) (pos - s); else *Dst = -1; break; } case StrLower: { if (Dst != Dom) *Dst = Dom->CastString(); StrLwr(Dst->Str()); break; } case StrUpper: { if (Dst != Dom) *Dst = Dom->CastString(); StrUpr(Dst->Str()); break; } case StrStrip: { auto s = Dom->Str(); if (s) { const char *Delimit = Arg.Length() > 0 ? Arg[0]->Str() : NULL; if (!Delimit) Delimit = WhiteSpace; auto start = s; auto end = s + Strlen(s); while (start < end && strchr(Delimit, *start)) start++; while (end > start && strchr(Delimit, end[-1])) end--; Dst->OwnStr(NewStr(start, end - start)); } else Dst->Empty(); break; } case StrSub: { auto s = Dom->Str(); if (s) { ssize_t Start = Arg.Length() > 0 ? Arg[0]->CastInt32() : 0; ssize_t End = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; ssize_t Len = strlen(s); if (End < 0 || End > Len) End = Len; if (Start < 0) Start = 0; if (Start <= End) Dst->OwnStr(NewStr(s + Start, End - Start)); else Dst->Empty(); } else Dst->Empty(); break; } default: { Dst->Empty(); if (Log) Log->Print("%p IDomCall warning: Unexpected string member %s (%s:%i).\n", CurrentScriptAddress, sName, _FL); Status = ScriptWarning; break; } } break; } default: { const char *Type = LVariant::TypeToString(Dom->Type); char t[32]; if (!Type) { sprintf_s(t, sizeof(t), "UnknownType(%i)", Dom->Type); Type = t; } Dst->Empty(); if (Log) { Log->Print("%s IDomCall warning: Unexpected type %s (Src=%s:%i IP=0x%x).\n", Code->AddrToSourceRef(CurrentScriptAddress), Type, _FL, CurrentScriptAddress); } Status = ScriptWarning; break; } } #else LVariant *Count = NULL; switch (c.r->Scope) { case SCOPE_GLOBAL: Count = &Code->Globals[c.r->Index]; c.r++; break; default: OnException(_FL, CurrentScriptAddress, "Unsupported scope."); return ScriptError; } int Args = Count->CastInt32(); for (int i=0; iPrint("%s%s", i ? ", " : "", c.r->GetStr()); #endif c.r++; } #if VM_DECOMP if (Log) Log->Print(")\n"); #endif #endif break; } case IBreakPoint: { #if VM_DECOMP if (Log) Log->Print("%p Debugger\n", CurrentScriptAddress-1); #elif VM_EXECUTE OnException(_FL, CurrentScriptAddress-1, "ShowDebugger"); return ScriptWarning; #endif break; } case IDebug: { #if VM_DECOMP if (Log) Log->Print("%p Debugger\n", CurrentScriptAddress-1); #elif VM_EXECUTE #ifdef WINDOWS __debugbreak(); #elif defined MAC __builtin_trap(); #elif defined LINUX Gtk::raise(SIGINT); #else #warning "Not impl." #endif #endif break; } #undef Resolve #undef GResolveRef \ No newline at end of file diff --git a/src/common/Coding/ScriptCompiler.cpp b/src/common/Coding/ScriptCompiler.cpp --- a/src/common/Coding/ScriptCompiler.cpp +++ b/src/common/Coding/ScriptCompiler.cpp @@ -1,3863 +1,3866 @@ /// \file #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/LexCpp.h" #include "lgi/common/LgiString.h" #include "ScriptingPriv.h" // #define DEBUG_SCRIPT_FILE "Mail Filters Menu.script" #define GetTok(c) ((c) < Tokens.Length() ? Tokens[c] : NULL) #define GetTokType(c) ((c) < Tokens.Length() ? ExpTok.Find(Tokens[c]) : TNone) #define GV_VARIANT GV_MAX const char *sDebugger = "Debugger"; int LFunctionInfo::_Infos = 0; enum LTokenType { #define _(type, str) T##type, #include "TokenType.h" #undef _ }; struct LinkFixup { int Tok; size_t Offset; int Args; LFunctionInfo *Func; }; struct Node { typedef LArray NodeExp; struct VariablePart { LVariant Name; NodeExp Array; bool Call; LArray Args; VariablePart() { Call = false; } ~VariablePart() { Args.DeleteObjects(); } }; // Hierarchy NodeExp Child; // One of the following are valid: LOperator Op = OpNull; // -or- bool Constant = false; int Tok = -1; LArray Lst; LTokenType ConstTok = TNone; // -or- LFunc *ContextFunc = NULL; LArray Args; // -or- LFunctionInfo *ScriptFunc = NULL; // -or- LArray Variable; // Used during building LVarRef Reg; LVarRef ArrayIdx; void Init() { Reg.Empty(); ArrayIdx.Empty(); } void SetOp(LOperator o, int t) { Init(); Op = o; Tok = t; } void SetConst(int t, LTokenType e) { Init(); Constant = true; Tok = t; ConstTok = e; } void SetConst(LArray &list_tokens, LTokenType e) { Init(); Constant = true; Lst = list_tokens; ConstTok = e; } void SetContextFunction(LFunc *m, int tok) { Init(); ContextFunc = m; Tok = tok; } void SetScriptFunction(LFunctionInfo *m, int tok) { Init(); ScriptFunc = m; Tok = tok; } void SetVar(char16 *Var, int t) { Init(); Variable[0].Name = Var; Tok = t; } bool IsVar() { return Variable.Length() > 0; } bool IsContextFunc() { return ContextFunc != 0; } bool IsScriptFunc() { return ScriptFunc != 0; } bool IsConst() { return Constant; } }; LCompiledCode::LCompiledCode() : Globals(SCOPE_GLOBAL), Debug(0, -1) { SysContext = NULL; UserContext = NULL; } LCompiledCode::LCompiledCode(LCompiledCode ©) : Globals(SCOPE_GLOBAL), Debug(0, -1) { *this = copy; } LCompiledCode::~LCompiledCode() { for (auto e: Externs) e->InUse = false; Externs.DeleteObjects(); } LCompiledCode &LCompiledCode::operator =(const LCompiledCode &c) { Globals = c.Globals; ByteCode = c.ByteCode; Types = c.Types; Debug = c.Debug; Methods = c.Methods; FileName = c.FileName; Source = c.Source; return *this; } LFunctionInfo *LCompiledCode::GetMethod(const char *Name, bool Create) { for (auto &m: Methods) { const char *Fn = m->GetName(); if (!strcmp(Fn, Name)) { return m; } } if (Create) { LAutoRefPtr n(new LFunctionInfo(Name)); if (n) { Methods.Add(n); return n; } } return 0; } int LCompiledCode::ObjectToSourceAddress(size_t ObjAddr) { auto Idx = Debug.Find(ObjAddr); return Idx < 0 ? 1 : Idx; } const char *LCompiledCode::AddrToSourceRef(size_t ObjAddr) { static char Status[256]; size_t Addr = ObjAddr; int LineNum = ObjectToSourceAddress(Addr); // Search for the start of the instruction... while (Addr > 0 && LineNum < 0) LineNum = ObjectToSourceAddress(--Addr); char *Dir = FileName ? strrchr(FileName, DIR_CHAR) : NULL; size_t PathLen = Dir ? Dir - FileName : 0; LString FileRef = FileName ? (PathLen > 24 ? Dir + 1 : FileName.Get()) : "(untitled)"; if (LineNum >= 0) sprintf_s( Status, sizeof(Status), "%s:%i", FileRef.Get(), LineNum); else sprintf_s(Status, sizeof(Status), "%s:0x%x", FileRef.Get(), (int)ObjAddr); return Status; } LVariant *LCompiledCode::Set(const char *Name, LVariant &v) { int i = Globals.Var(Name, true); if (i >= 0) { Globals[i] = v; return &Globals[i]; } return 0; } template void UnEscape(T *t) { T *i = t, *o = t; while (*i) { if (*i == '\\') { i++; switch (*i) { case '\\': *o++ = '\\'; i++; break; case 'n': *o++ = '\n'; i++; break; case 'r': *o++ = '\r'; i++; break; case 't': *o++ = '\t'; i++; break; case 'b': *o++ = '\b'; i++; break; } } else *o++ = *i++; } *o++ = 0; } class TokenRanges { LArray FileNames; struct Range { int Start, End; ssize_t File; int Line; }; LArray Ranges; char fl[MAX_PATH_LEN + 32]; public: TokenRanges() { Empty(); } void Empty() { Ranges.Length(0); } ssize_t Length() { return Ranges.Length(); } /// Gets the file/line at a given token const char *operator [](ssize_t Tok) { Range *r = NULL; for (unsigned i=0; i= rng->Start && Tok <= rng->End) { r = rng; break; } } if (!r) r = &Ranges.Last(); if (r->File >= (ssize_t)FileNames.Length()) { LAssert(!"Invalid file index."); return "#err: invalid file index"; } else { sprintf_s(fl, sizeof(fl), "%s:%i", FileNames[r->File].Get(), r->Line); } return fl; } const char *GetLine(int Line) { if (Ranges.Length() > 0) { Range &r = Ranges.Last(); if (r.File < (ssize_t)FileNames.Length()) sprintf_s(fl, sizeof(fl), "%s:%i", FileNames[r.File].Get(), Line); else return "#err: invalid file index."; } else { sprintf_s(fl, sizeof(fl), "$unknown:%i", Line); } return fl; } ssize_t GetFileIndex(const char *FileName) { for (unsigned i=0; iFile != FileId || r->Line != Line) { // Start a new range... r = &Ranges.New(); r->Start = r->End = TokIndex; r->File = FileId; r->Line = Line; } else { r->End = TokIndex; } } }; /// Scripting language compiler implementation class LCompilerPriv : public LCompileTools, public LScriptUtils { LHashTbl, LVariantType> Types; size_t JumpLoc; LArray> FuncMem; public: LScriptContext *SysCtx; LScriptContext *UserCtx; LCompiledCode *Code; LStream *Log; LArray Tokens; TokenRanges Lines; char16 *Script; LHashTbl, LFunc*> Methods; uint64_t Regs; LArray Scopes; LArray Fixups; LHashTbl, char16*> Defines; LHashTbl, LTokenType> ExpTok; LDom *ScriptArgs; LVarRef ScriptArgsRef; bool ErrShowFirstOnly; LArray ErrLog; bool Debug; #ifdef _DEBUG LString::Array RegAllocators; #endif LCompilerPriv() { Debug = false; ErrShowFirstOnly = true; SysCtx = NULL; UserCtx = NULL; Code = 0; Log = 0; Script = 0; Regs = 0; ScriptArgs = NULL; ScriptArgsRef.Empty(); #define LNULL NULL #define _(type, str) if (str) ExpTok.Add(L##str, T##type); #include "TokenType.h" #undef _ #undef LNULL Types.Add("int", GV_INT32); Types.Add("short", GV_INT32); Types.Add("long", GV_INT32); Types.Add("int8", GV_INT32); Types.Add("int16", GV_INT32); Types.Add("int32", GV_INT32); Types.Add("int64", GV_INT64); Types.Add("uint8", GV_INT32); Types.Add("uint16", GV_INT32); Types.Add("uint32", GV_INT32); Types.Add("uint64", GV_INT64); Types.Add("bool", GV_BOOL); Types.Add("boolean", GV_BOOL); Types.Add("double", GV_DOUBLE); Types.Add("float", GV_DOUBLE); Types.Add("char", GV_STRING); Types.Add("LDom", GV_DOM); Types.Add("void", GV_VOID_PTR); Types.Add("LDateTime", GV_DATETIME); Types.Add("GHashTable", GV_HASHTABLE); Types.Add("LOperator", GV_OPERATOR); Types.Add("LView", GV_GVIEW); Types.Add("LMouse", GV_LMOUSE); Types.Add("LKey", GV_LKEY); Types.Add("LVariant", GV_VARIANT); // Types.Add("binary", GV_BINARY); // Types.Add("List", GV_LIST); // Types.Add("LDom&", GV_DOMREF); } ~LCompilerPriv() { Empty(); } void Empty() { SysCtx = NULL; UserCtx = NULL; DeleteObj(Code); Log = 0; Tokens.DeleteArrays(); Lines.Empty(); DeleteArray(Script); Defines.DeleteArrays(); Methods.Empty(); } /// Prints the error message bool OnError ( /// The line number (less than 0) or Token index (greater than 0) int LineOrTok, /// The format for the string (printf) const char *Msg, /// Variable argument list ... ) { if (!ErrShowFirstOnly || ErrLog.Length() == 0) { char Buf[512]; va_list Arg; va_start(Arg, Msg); #ifndef WIN32 #define _vsnprintf vsnprintf #endif vsprintf_s(Buf, sizeof(Buf), Msg, Arg); Log->Print("%s - Error: %s\n", LineOrTok < 0 ? Lines.GetLine(-LineOrTok) : Lines[LineOrTok], Buf); va_end(Arg); ErrLog.New() = Buf; } return false; // Always return false to previous caller } void DebugInfo(int Tok) { const char *Line = Lines[Tok]; if (Line) { const char *c = strrchr(Line, ':'); if (c) { Code->Debug.Add(Code->ByteCode.Length(), ::atoi(c+1)); } } } /// Assemble a zero argument instruction bool Asm0(int Tok, uint8_t Op) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1)) { - GPtr p; + 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)) { - GPtr p; + 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)) { - GPtr p; + 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) )) { - GPtr p; + 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) )) { - GPtr p; + 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()) )) { - GPtr p; + 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; - LAutoString IncCode; + LString IncCode; v.OwnStr(File.Release()); if (UserCtx) { - IncCode.Reset(UserCtx->GetIncludeFile(v.Str())); + 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())) { - IncCode.Reset(LReadTextFile(v.Str())); - if (IncCode) + 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); - GPtr p; + 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); - GPtr p; + 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)) { - GPtr p; + 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) { - GPtr p; + 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) { - GPtr p; + 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 { - GPtr p; + 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) { - GHostFunc *f = SysContext->GetCommands(); + 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) { - GHostFunc *f = d->UserCtx->GetCommands(); + LHostFunc *f = d->UserCtx->GetCommands(); for (int i=0; f[i].Method; i++) { f[i].Context = d->UserCtx; if (!d->Methods.Find(f[i].Method)) d->Methods.Add(f[i].Method, f+i); else { LgiTrace("%s:%i - Conflicting name of method in application's context: '%s'\n", _FL, f[i].Method.Get()); LAssert(!"Conflicting name of method in application's context."); } } } if (!Code) Code.Reset(new LCompiledCode); bool Status = false; d->Code = dynamic_cast(Code.Get()); if (d->Code) { d->Code->UserContext = UserContext; d->Code->SysContext = SysContext; d->Code->SetSource(FileName, Script); bool LexResult = d->Lex((char*)Script, FileName); if (LexResult) { d->ScriptArgs = Args; Status = d->Compile(); } else { d->OnError(0, "Failed to lex script.\n"); } } else { d->OnError(0, "Allocation failed.\n"); } d->Code = NULL; return Status; } ////////////////////////////////////////////////////////////////////// class LScriptEnginePrivate { public: LViewI *Parent; SystemFunctions SysContext; LScriptContext *UserContext; LCompiledCode *Code; - LVmDebuggerCallback *Callback; + LVmCallback *Callback; LVariant ReturnValue; LScriptEnginePrivate() { UserContext = NULL; Parent = NULL; Code = NULL; Callback = NULL; } }; -LScriptEngine::LScriptEngine(LViewI *parent, LScriptContext *UserContext, LVmDebuggerCallback *Callback) +LScriptEngine::LScriptEngine(LViewI *parent, LScriptContext *UserContext, LVmCallback *Callback) { d = new LScriptEnginePrivate; d->Parent = parent; d->UserContext = UserContext; d->Callback = Callback; d->SysContext.SetEngine(this); } LScriptEngine::~LScriptEngine() { DeleteObj(d); } LCompiledCode *LScriptEngine::GetCurrentCode() { return d->Code; } bool LScriptEngine::Compile(LAutoPtr &Obj, LScriptContext *UserContext, const char *Script, const char *FileName, LDom *Args) { if (!Script) { LAssert(!"Param error"); return NULL; } LCompiler Comp; return Comp.Compile(Obj, &d->SysContext, UserContext ? UserContext : d->UserContext, FileName, Script, Args); } LExecutionStatus LScriptEngine::Run(LCompiledCode *Obj, LVariant *Ret, const char *TempPath) { LExecutionStatus Status = ScriptError; d->Code = Obj; if (d->Code) { LVirtualMachine Vm(d->Callback); if (TempPath) Vm.SetTempPath(TempPath); Status = Vm.Execute(d->Code, 0, NULL, true, Ret ? Ret : &d->ReturnValue); d->Code = NULL; } return Status; } LExecutionStatus LScriptEngine::RunTemporary(LCompiledCode *Obj, char *Script, LVariant *Ret) { LExecutionStatus Status = ScriptError; LCompiledCode *Code = dynamic_cast(Obj); if (Script && Code) { LAutoPtr Temp(new LCompiledCode(*Code)); uint32_t TempLen = (uint32_t) Temp->Length(); d->Code = Temp; LCompiler Comp; if (Comp.Compile(Temp, &d->SysContext, d->UserContext, Temp->GetFileName(), Script, NULL)) { LVirtualMachine Vm(d->Callback); Status = Vm.Execute(dynamic_cast(Temp.Get()), TempLen, NULL, true, Ret ? Ret : &d->ReturnValue); } d->Code = NULL; } return Status; } bool LScriptEngine::EvaluateExpression(LVariant *Result, LDom *VariableSource, const char *Expression) { if (!Result || !VariableSource || !Expression) { LAssert(!"Param error"); return false; } // Create trivial script to evaluate the expression LString a; a.Printf("return %s;", Expression); // Compile the script LCompiler Comp; LAutoPtr Obj; if (!Comp.Compile(Obj, NULL, NULL, NULL, a, VariableSource)) { LAssert(0); return false; } // Execute the script LVirtualMachine Vm(d->Callback); LCompiledCode *Code = dynamic_cast(Obj.Get()); auto ReturnVal = Result ? Result : &d->ReturnValue; LExecutionStatus s = Vm.Execute(Code, 0, NULL, true, ReturnVal); if (s != ScriptSuccess) { return false; } if (ReturnVal->Type == GV_INT64 && !(ReturnVal->Value.Int64 >> 32)) { *ReturnVal = ReturnVal->CastInt32(); } return true; } LStream *LScriptEngine::GetConsole() { if (d->SysContext.GetLog()) return d->SysContext.GetLog(); if (d->UserContext && d->UserContext->GetLog()) return d->UserContext->GetLog(); return NULL; } bool LScriptEngine::SetConsole(LStream *t) { d->SysContext.SetLog(t); if (d->UserContext) d->UserContext->SetLog(t); return true; } bool LScriptEngine::CallMethod(LCompiledCode *Obj, const char *Method, LScriptArguments &Args) { LCompiledCode *Code = dynamic_cast(Obj); if (!Code || !Method) return false; LFunctionInfo *i = Code->GetMethod(Method); if (!i) return false; LVirtualMachine Vm(d->Callback); Args.Vm = &Vm; LExecutionStatus Status = Vm.ExecuteFunction(Code, i, Args, NULL); Args.Vm = NULL; return Status != ScriptError; } LScriptContext *LScriptEngine::GetSystemContext() { return &d->SysContext; } diff --git a/src/common/Coding/ScriptLibrary.cpp b/src/common/Coding/ScriptLibrary.cpp --- a/src/common/Coding/ScriptLibrary.cpp +++ b/src/common/Coding/ScriptLibrary.cpp @@ -1,1189 +1,1241 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/SubProcess.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "ScriptingPriv.h" ////////////////////////////////////////////////////////////////////////////////////// char16 sChar[] = L"char"; char16 sInt[] = { 'i','n','t', 0 }; char16 sUInt[] = { 'u','i','n','t', 0 }; char16 sInt32[] = { 'i','n','t','3','2', 0 }; char16 sUInt32[] = { 'u','i','n','t','3','2', 0 }; char16 sInt64[] = { 'i','n','t','6','4', 0 }; char16 sHWND[] = { 'H','W','N','D', 0 }; char16 sDWORD[] = { 'D','W','O','R','D', 0 }; char16 sLPTSTR[] = { 's','L','P','T','S','T','R', 0 }; char16 sLPCTSTR[] = { 's','L','P','C','T','S','T','R', 0 }; char16 sElse[] = { 'e','l','s','e', 0 }; char16 sIf[] = { 'i','f',0 }; char16 sFunction[] = { 'f','u','n','c','t','i','o','n',0 }; char16 sExtern[] = { 'e','x','t','e','r','n',0 }; char16 sFor[] = { 'f','o','r',0 }; char16 sWhile[] = { 'w','h','i','l','e',0 }; char16 sReturn[] = { 'r','e','t','u','r','n',0 }; char16 sInclude[] = { 'i','n','c','l','u','d','e',0 }; char16 sDefine[] = { 'd','e','f','i','n','e',0 }; char16 sStruct[] = { 's','t','r','u','c','t',0 }; char16 sTrue[] = { 't','r','u','e',0 }; char16 sFalse[] = { 'f','a','l','s','e',0 }; char16 sNull[] = { 'n','u','l','l',0 }; char16 sOutParam[] = { '_','o','u','t','_',0}; char16 sHash[] = { '#', 0 }; char16 sPeriod[] = { '.', 0 }; char16 sComma[] = { ',', 0 }; char16 sSemiColon[] = { ';', 0 }; char16 sStartRdBracket[] = { '(', 0 }; char16 sEndRdBracket[] = { ')', 0 }; char16 sStartSqBracket[] = { '[', 0 }; char16 sEndSqBracket[] = { ']', 0 }; char16 sStartCurlyBracket[] = { '{', 0 }; char16 sEndCurlyBracket[] = { '}', 0 }; ////////////////////////////////////////////////////////////////////////////////////// -LExecutionStatus GHostFunc::Call(LScriptContext *Ctx, LScriptArguments &Args) +LExecutionStatus LHostFunc::Call(LScriptContext *Ctx, LScriptArguments &Args) { return (Ctx->*(Func))(Args) ? ScriptSuccess : ScriptError; } const char *InstToString(GInstruction i) { #undef _i #define _i(name, opcode, desc) \ case name: return desc; switch (i) { AllInstructions } return "#err"; } LStream LScriptArguments::NullConsole; ////////////////////////////////////////////////////////////////////////////////////// int LScriptUtils::atoi(char16 *s) { int i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::atoi(b); } return i; } int64 LScriptUtils::atoi64(char16 *s) { int64 i = 0; if (s) { #ifdef _MSC_VER i = _wtoi64(s); #else char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = strtoll(b, 0, 10); #endif } return i; } double LScriptUtils::atof(char16 *s) { double i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::atof(b); } return i; } int LScriptUtils::htoi(char16 *s) { int i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::htoi(b); } return i; } ////////////////////////////////////////////////////////////////////////////////////// SystemFunctions::SystemFunctions() { Engine = NULL; Log = NULL; #ifdef WINNATIVE Brk = NULL; #endif } SystemFunctions::~SystemFunctions() { } LStream *SystemFunctions::GetLog() { return Log; } bool SystemFunctions::SetLog(LStream *log) { LAssert(Log == NULL); Log = log; return true; } void SystemFunctions::SetEngine(LScriptEngine *Eng) { Engine = Eng; } bool SystemFunctions::Assert(LScriptArguments &Args) { *Args.GetReturn() = true; if (Args.Length() == 0) return true; auto v = Args[0]->CastInt32(); if (!v) { const char *Msg = Args.Length() > 1 ? Args[1]->CastString() : NULL; *Args.GetReturn() = false; Args.Throw(NULL, -1, Msg); } return true; } bool SystemFunctions::DebuggerEnabled(LScriptArguments &Args) { if (Args.Length() == 0) { LAssert(!"Wrong args."); return false; } Args.GetVm()->SetDebuggerEnabled(Args[0]->CastInt32() != 0); return true; } bool SystemFunctions::Throw(LScriptArguments &Args) { const char *Msg = Args.Length() > 0 ? Args[0]->CastString() : NULL; Args.Throw(NULL, -1, Msg); return true; } bool SystemFunctions::LoadString(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } *Args.GetReturn() = LLoadString(Args[0]->CastInt32()); return true; } bool SystemFunctions::Sprintf(LScriptArguments &Args) { if (Args.Length() < 1) { LAssert(!"Wrong args."); return false; } char *Fmt = Args[0]->Str(); if (!Fmt) return false; #if defined(LINUX) || defined(MAC) // No support for sprintf with generated args... hack a string up // Formatting widths etc not supported. LArray s; int i = 1; for (char *f = Fmt; *f; f++) { if (f[0] == '%' && f[1] != '%') { f++; // Skip '%' // char *Fmt = f; while (*f && !IsAlpha(*f)) f++; // Skip formatting.. if (i >= Args.Length()) break; // No more arguments... switch (*f) { case 's': { // String... char *v = Args[i]->CastString(); if (v) s.Add(v, strlen(v)); else s.Add((char*)"(null)", 4); break; } case 'c': { char *Str = Args[i]->Str(); s.Add(Str ? *Str : '?'); break; } case 'f': case 'g': { break; } case 'u': case 'd': case 'i': { // Int... LString v; v.Printf("%i", Args[i]->CastInt32()); s.Add(v.Get(), v.Length()); break; } } i++; } else s.Add(*f); } s.Add(0); // NULL terminate *Args.GetReturn() = s.AddressOf(); #else LArray Params; va_list a; unsigned i = 1; for (char *f = Fmt; *f; f++) { if (f[0] == '%' && f[1] != '%') { char *t = f + 1; while (*t && !IsAlpha(*t)) t++; if (i >= Args.Length()) { LAssert(!"Not enough args."); break; } switch (*t) { case 's': { Params.Add((UNativeInt)Args[i++]->Str()); break; } case 'c': { char *Str = Args[i++]->Str(); Params.Add(Str ? *Str : '?'); break; } case 'f': case 'g': { union tmp { double Dbl; struct { uint32_t High; uint32_t Low; }; } Tmp; Tmp.Dbl = Args[i++]->CastDouble(); Params.Add(Tmp.High); Params.Add(Tmp.Low); break; } default: { Params.Add(Args[i++]->CastInt32()); break; } } f = *t ? t + 1 : t; } } a = (va_list) &Params[0]; #ifndef WIN32 #define _vsnprintf vsnprintf #endif char Buf[1024]; vsprintf_s(Buf, sizeof(Buf), Fmt, a); *Args.GetReturn() = Buf; #endif return true; } bool SystemFunctions::ReadTextFile(LScriptArguments &Args) { if (Args.Length() == 1 && LFileExists(Args[0]->CastString())) { if (Args.GetReturn()->OwnStr(::LReadTextFile(Args[0]->CastString()))) return true; } return false; } bool SystemFunctions::WriteTextFile(LScriptArguments &Args) { if (Args.Length() == 2) { LFile f; if (f.Open(Args[0]->CastString(), O_WRITE)) { f.SetSize(0); LVariant *v = Args[1]; if (v) { switch (v->Type) { default: break; case GV_STRING: { size_t Len = strlen(v->Value.String); *Args.GetReturn() = f.Write(v->Value.String, Len) == Len; return true; break; } case GV_BINARY: { *Args.GetReturn() = f.Write(v->Value.Binary.Data, v->Value.Binary.Length) == v->Value.Binary.Length; return true; break; } } } } } return false; } LView *SystemFunctions::CastLView(LVariant &v) { switch (v.Type) { default: break; case GV_DOM: return dynamic_cast(v.Value.Dom); case GV_GVIEW: return v.Value.View; } return 0; } -bool SystemFunctions::WaitForReturn(LScriptArguments &Args) +bool SystemFunctions::SelectFiles(LScriptArguments &Args) { - while (Args.GetReturn()->Type != GV_NULL) + Args.GetReturn()->Empty(); + if (Args.Length() < 2) { - // This should only ever loop on Haiku... - LYield(); - LSleep(10); + Args.Throw(_FL, "SelectFiles(Parent, Callback[, FileTypes[, InitialDir[, MultiSelect[, SaveAs]]]]) expects at least 2 arguments."); + return false; } - return true; -} + auto Ctx = Args.GetVm()->GetCallback(); + if (!Ctx) + { + Args.Throw(_FL, "SelectFiles(...) requires a valid callback context."); + return false; + } -bool SystemFunctions::SelectFiles(LScriptArguments &Args) -{ LFileSelect *s = new LFileSelect; + if (!s) + return false; - Args.GetReturn()->Empty(); - if (Args.Length() > 0) - s->Parent(CastLView(*Args[0])); - - auto t = LString(Args.Length() > 1 ? Args[1]->CastString() : NULL).SplitDelimit(",;:"); - for (auto c: t) + s->Parent(CastLView(*Args[0])); + auto Callback = Args[1]->Str(); + auto Types = LString(Args.IdxCheck(2) ? Args[2]->CastString() : NULL).SplitDelimit(",;:"); + for (auto c: Types) { char *sp = strrchr(c, ' '); if (sp) { *sp++ = 0; s->Type(sp, c); } else { char *dot = strrchr(c, '.'); if (dot) { char Type[256]; sprintf_s(Type, sizeof(Type), "%s files", dot + 1); s->Type(Type, c); } } } s->Type("All Files", LGI_ALL_FILES); - s->InitialDir(Args.Length() > 2 ? Args[2]->CastString() : 0); - s->MultiSelect(Args.Length() > 3 ? Args[3]->CastInt32() != 0 : true); - bool SaveAs = Args.Length() > 4 ? Args[4]->CastInt32() != 0 : false; + s->InitialDir (Args.IdxCheck(3) ? Args[3]->CastString() : 0); + s->MultiSelect(Args.IdxCheck(4) ? Args[4]->CastInt32() != 0 : true); + bool SaveAs = Args.IdxCheck(5) ? Args[5]->CastInt32() != 0 : false; - auto Process = [&Args](LFileSelect *s, bool ok) + auto Process = [Callback=LString(Callback),Ctx](LFileSelect *s, bool ok) { if (ok) { - Args.GetReturn()->SetList(); - auto Lst = Args.GetReturn()->Value.Lst; - for (unsigned i=0; iLength(); i++) - Lst->Insert(new LVariant((*s)[i])); + LVirtualMachine Vm; + LScriptArguments Args(&Vm); + + LVariant *v = new LVariant; + if (auto Lst = v->SetList()) + { + for (unsigned i=0; iLength(); i++) + { + auto path = (*s)[i]; + Lst->Insert(new LVariant(path)); + } + } + Args.Add(v); + + Ctx->CallCallback(Callback, Args); + Args.DeleteObjects(); } - else *Args.GetReturn() = false; + delete s; }; if (SaveAs) s->Save(Process); else s->Open(Process); - return WaitForReturn(Args); + return true; } bool SystemFunctions::SelectFolder(LScriptArguments &Args) { + Args.GetReturn()->Empty(); + if (Args.Length() < 2) + { + Args.Throw(_FL, "SelectFolder(Parent, Callback[, InitialDir]) expects at least 2 arguments."); + return false; + } + + auto Ctx = Args.GetVm()->GetCallback(); + if (!Ctx) + { + Args.Throw(_FL, "SelectFiles(...) requires a valid callback context."); + return false; + } + LFileSelect *s = new LFileSelect; + if (!s) + return false; - if (Args.Length() > 0) - s->Parent(CastLView(*Args[0])); - s->InitialDir(Args.Length() > 1 ? Args[1]->CastString() : 0); + s->Parent(CastLView(*Args[0])); + auto Callback = Args[1]->Str(); + if (Args.IdxCheck(2)) + s->InitialDir(Args[2]->CastString()); - Args.GetReturn()->Empty(); - - s->OpenFolder([&Args](LFileSelect *s, bool ok) + s->OpenFolder([Ctx, Callback=LString(Callback)](auto s, bool ok) { if (ok) - *Args.GetReturn() = s->Name(); - else - *Args.GetReturn() = false; + { + LVirtualMachine Vm; + LScriptArguments Args(&Vm); + Args.Add(new LVariant(s->Name())); + Ctx->CallCallback(Callback, Args); + Args.DeleteObjects(); + } + delete s; }); - return WaitForReturn(Args); + return true; } bool SystemFunctions::Sleep(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } LSleep(Args[0]->CastInt32()); return true; } bool SystemFunctions::ToString(LScriptArguments &Args) { LStringPipe p; const char *Sep = ", "; for (unsigned i=0; iToString(); p.Print("%s%s", i?Sep:"", s.Get()); } Args.GetReturn()->OwnStr(p.NewStr()); return true; } bool SystemFunctions::Print(LScriptArguments &Args) { LStream *Out = Log ? Log : (Engine ? Engine->GetConsole() : NULL); for (unsigned n=0; Out && nWrite("NULL", 4); continue; } #if 1 size_t Len = strlen(f); Out->Write(f, Len); #else char *i = f, *o = f; for (; *i; i++) { if (*i == '\\') { i++; switch (*i) { case 'n': *o++ = '\n'; break; case 'r': *o++ = '\r'; break; case 't': *o++ = '\t'; break; case '\\': *o++ = '\\'; break; case '0': *o++ = 0; break; } } else { *o++ = *i; } } *o = 0; Out->Write(f, o - f); #endif } return true; } bool SystemFunctions::FormatSize(LScriptArguments &Args) { if (Args.Length() != 1) return false; char s[64]; LFormatSize(s, sizeof(s), Args[0]->CastInt64()); *Args.GetReturn() = s; return true; } bool SystemFunctions::ClockTick(LScriptArguments &Args) { *Args.GetReturn() = (int64)LCurrentTime(); return true; } bool SystemFunctions::Now(LScriptArguments &Args) { Args.GetReturn()->Empty(); Args.GetReturn()->Type = GV_DATETIME; Args.GetReturn()->Value.Date = new LDateTime; Args.GetReturn()->Value.Date->SetNow(); return true; } bool SystemFunctions::New(LScriptArguments &Args) { if (Args.Length() < 1 || !Args[0]) { LAssert(!"Wrong args."); return false; } Args.GetReturn()->Empty(); char *sType = Args[0]->CastString(); if (!sType) return false; if (IsDigit(*sType)) { // Binary block int Bytes = ::atoi(sType); if (!Bytes) return false; return Args.GetReturn()->SetBinary(Bytes, new char[Bytes], true); } LVariant *Ret = Args.GetReturn(); LDomProperty Type = LStringToDomProp(sType); switch (Type) { case TypeList: { Ret->SetList(); break; } case TypeHashTable: { Ret->SetHashTable(); break; } case TypeSurface: { Ret->Empty(); Ret->Type = GV_LSURFACE; if ((Ret->Value.Surface.Ptr = new LMemDC)) { Ret->Value.Surface.Ptr->IncRef(); Ret->Value.Surface.Own = true; } break; } case TypeFile: { Ret->Empty(); #if 1 Ret->Type = GV_STREAM; Ret->Value.Stream.Ptr = new LFile; if (Ret->Value.Stream.Ptr) Ret->Value.Stream.Own = true; #else Ret->Type = GV_GFILE; if ((Ret->Value.File.Ptr = new LFile)) { Ret->Value.File.Ptr->AddRef(); Ret->Value.File.Own = true; } #endif break; } case TypeDateTime: { Ret->Empty(); Ret->Type = GV_DATETIME; Ret->Value.Date = new LDateTime; break; } default: { Ret->Empty(); LCompiledCode *c = Engine ? Engine->GetCurrentCode() : NULL; if (!c) return false; LAutoWString o(Utf8ToWide(sType)); LCustomType *t = c->GetType(o); if (t) { int ArrayLength = Args.Length() > 1 ? Args[1]->CastInt32() : 1; if (ArrayLength > 0) { Ret->Type = GV_CUSTOM; Ret->Value.Custom.Dom = t; Ret->Value.Custom.Data = new uint8_t[t->Sizeof() * ArrayLength]; } } } } return true; } bool SystemFunctions::Len(LScriptArguments &Args) { size_t i = 0; for (LVariant *v: Args) { switch (v->Type) { case GV_LIST: i += v->Value.Lst->Length(); break; case GV_HASHTABLE: i += v->Value.Hash->Length(); break; case GV_BINARY: i += v->Value.Binary.Length; break; case GV_STRING: i += Strlen(v->Value.String); break; case GV_WSTRING: i += Strlen(v->Value.WString); break; default: i += 1; break; } } *Args.GetReturn() = i; return true; } bool SystemFunctions::Delete(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } LVariant *v = Args[0]; if (v->Type == GV_CUSTOM) { DeleteArray(v->Value.Custom.Data); v->Empty(); } else { v->Empty(); } *Args.GetReturn() = true; return true; } class LFileListEntry : public LDom { bool Folder; LVariant Name; int64 Size; LDateTime Modified; public: LFileListEntry(LDirectory *d) { Folder = d->IsDir(); Name = d->GetName(); Size = d->GetSize(); Modified.Set(d->GetLastWriteTime()); } bool GetVariant(const char *Var, LVariant &Value, const char *Arr = NULL) override { LDomProperty p = LStringToDomProp(Var); switch (p) { case ObjName: Value = Name; break; case ObjLength: Value = Size; break; case FileFolder: Value = Folder; break; case FileModified: Value = &Modified; break; default: return false; } return true; } }; bool SystemFunctions::DeleteFile(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } char *f = Args[0]->CastString(); if (f) *Args.GetReturn() = FileDev->Delete(Args[0]->CastString()); else *Args.GetReturn() = false; return true; } bool SystemFunctions::CurrentScript(LScriptArguments &Args) { LCompiledCode *Code; if (Engine && (Code = Engine->GetCurrentCode())) { *Args.GetReturn() = Code->GetFileName(); return true; } return false; } bool SystemFunctions::PathExists(LScriptArguments &Args) { if (Args.Length() == 0) { LAssert(!"Wrong args."); return false; } LDirectory d; if (d.First(Args[0]->CastString(), NULL)) { if (d.IsDir()) *Args.GetReturn() = 2; else *Args.GetReturn() = 1; } else { *Args.GetReturn() = 0; } return true; } bool SystemFunctions::PathJoin(LScriptArguments &Args) { char p[MAX_PATH_LEN] = ""; for (unsigned i=0; iCastString(); if (i) LMakePath(p, sizeof(p), p, s); else strcpy_s(p, sizeof(p), s); } if (*p) *Args.GetReturn() = p; else Args.GetReturn()->Empty(); return true; } bool SystemFunctions::PathSep(LScriptArguments &Args) { *Args.GetReturn() = DIR_STR; return true; } bool SystemFunctions::ListFiles(LScriptArguments &Args) { if (Args.Length() < 1) { - LAssert(!"Wrong args."); + Args.GetReturn()->Empty(); + Args.Throw(_FL, "ListFiles(FolderPath[, FilterPattern]) expects at least one argument."); return false; } Args.GetReturn()->SetList(); + + auto Folder = Args[0]->CastString(); + auto Pattern = Args.Length() > 1 ? Args[1]->CastString() : NULL; + LDirectory d; - char *Pattern = Args.Length() > 1 ? Args[1]->CastString() : 0; - - char *Folder = Args[0]->CastString(); - for (int b=d.First(Folder); b; b=d.Next()) + for (auto b=d.First(Folder); b; b=d.Next()) { if (!Pattern || MatchStr(Pattern, d.GetName())) - { Args.GetReturn()->Value.Lst->Insert(new LVariant(new LFileListEntry(&d))); - } } return true; } bool SystemFunctions::CreateSurface(LScriptArguments &Args) { Args.GetReturn()->Empty(); - if (Args.Length() < 2) { - LAssert(!"Wrong args."); + Args.Throw(_FL, "CreateSurface(x, y[, Bits|ColourSpace]) expects at least two arguments."); return false; } - int x = Args[0]->CastInt32(); - int y = Args[1]->CastInt32(); + auto x = Args[0]->CastInt32(); + auto y = Args[1]->CastInt32(); LColourSpace Cs = CsNone; if (Args.Length() > 2) { LVariant *Type = Args[2]; const char *c; if (Type->IsInt()) { // Bit depth... convert to default Colour Space. Cs = LBitsToColourSpace(Type->CastInt32()); } else if ((c = Type->Str())) { // Parse string colour space def Cs = LStringToColourSpace(Type->Str()); } } if (!Cs) // Catch all error cases and make it the default screen depth. Cs = GdcD->GetColourSpace(); - if ((Args.GetReturn()->Value.Surface.Ptr = new LMemDC(x, y, Cs))) + auto r = Args.GetReturn(); + if ((r->Value.Surface.Ptr = new LMemDC(x, y, Cs))) { - Args.GetReturn()->Type = GV_LSURFACE; - Args.GetReturn()->Value.Surface.Own = true; - Args.GetReturn()->Value.Surface.Ptr->IncRef(); + r->Type = GV_LSURFACE; + r->Value.Surface.Own = true; + r->Value.Surface.Ptr->IncRef(); } return true; } bool SystemFunctions::ColourSpaceToString(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() != 1) { - LAssert(!"Wrong args."); + Args.Throw(_FL, "ColourSpaceToString(ColourSpaceInt) expects at least one argument."); return false; } *Args.GetReturn() = LColourSpaceToString((LColourSpace)Args[0]->CastInt32()); return true; } bool SystemFunctions::StringToColourSpace(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() != 1) { - LAssert(!"Wrong args."); + Args.Throw(_FL, "StringToColourSpace(ColourSpaceStr) expects at least one argument."); return false; } *Args.GetReturn() = (int)LStringToColourSpace(Args[0]->Str()); return true; } bool SystemFunctions::MessageDlg(LScriptArguments &Args) { if (Args.Length() < 2) { - LAssert(!"Wrong args."); + Args.Throw(_FL, "MessageDlg(Parent, Message[, Title[, Buttons]]) expects at least 2 arguments."); return false; } - LViewI *Parent = CastLView(*Args[0]); - auto *Msg = Args[1]->Str(); - auto *Title = Args.IdxCheck(2) ? Args[2]->Str() : LAppInst->Name(); - uint32_t Btns = Args.IdxCheck(3) ? Args[3]->CastInt32() : MB_OK; + auto Parent = CastLView(*Args[0]); + auto Msg = Args[1]->Str(); + auto Title = Args.IdxCheck(2) ? Args[2]->Str() : LAppInst->Name(); + auto Btns = Args.IdxCheck(3) ? Args[3]->CastInt32() : MB_OK; auto Btn = LgiMsg(Parent, Msg, Title, Btns); *Args.GetReturn() = Btn; return true; } bool SystemFunctions::GetInputDlg(LScriptArguments &Args) { - if (Args.Length() < 4) + if (Args.Length() < 5) { - LAssert(!"Wrong args."); + Args.Throw(_FL, "GetInputDlg(Parent, DefaultInput, Message, Title, IsPassword, Callback) expects 5 arguments."); + return false; + } + + auto Ctx = Args.GetVm()->GetCallback(); + if (!Ctx) + { + Args.Throw(_FL, "GetInputDlg requires a valid VM context."); return false; } - LViewI *Parent = CastLView(*Args[0]); - char *InitVal = Args[1]->Str(); - char *Msg = Args[2]->Str(); - char *Title = Args[3]->Str(); - bool Pass = Args.Length() > 4 ? Args[4]->CastInt32() != 0 : false; + auto Parent = CastLView(*Args[0]); + auto InitVal = Args[1]->Str(); + auto Msg = Args[2]->Str(); + auto Title = Args[3]->Str(); + auto Pass = Args[4]->CastInt32() != 0; + auto Callback = Args[5]->Str(); + + Args.GetReturn()->Empty(); - LInput *Dlg = new LInput(Parent, InitVal, Msg, Title, Pass); - Dlg->DoModal([Dlg, &Args](auto d, auto code) + auto Dlg = new LInput(Parent, InitVal, Msg, Title, Pass); + Dlg->DoModal([this, Dlg, Ctx, Callback=LString(Callback)](auto d, auto ok) { - *Args.GetReturn() = code ? Dlg->GetStr() : -1; + if (ok) + { + LVirtualMachine Vm; + LScriptArguments Args(&Vm); + Args.Add(new LVariant(Dlg->GetStr())); + Ctx->CallCallback(Callback, Args); + Args.DeleteObjects(); + } delete Dlg; }); - return WaitForReturn(Args); + // Don't wait here... + return true; } bool SystemFunctions::GetViewById(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } LViewI *Parent = CastLView(*Args[0]); int Id = Args[1]->CastInt32(); if (!Parent || Id <= 0) return false; if (Parent->GetViewById(Id, Args.GetReturn()->Value.View)) { Args.GetReturn()->Type = GV_GVIEW; } return true; } bool SystemFunctions::Execute(LScriptArguments &Args) { if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } LStringPipe p; char *Exe = Args[0]->CastString(); char *Arguments = Args[1]->CastString(); LSubProcess e(Exe, Arguments); bool Status = e.Start(); if (Status) { e.Communicate(&p); LAutoString o(p.NewStr()); *Args.GetReturn() = o; } else if (Log) { uint32_t ErrCode = e.GetErrorCode(); LString ErrMsg = LErrorCodeToString(ErrCode); if (ErrMsg) Log->Print("Error: Execute(\"%s\",\"%s\") failed with '%s'\n", Exe, Arguments, ErrMsg.Get()); else Log->Print("Error: Execute(\"%s\",\"%s\") failed with '0x%x'\n", Exe, Arguments, ErrCode); } return Status; } bool SystemFunctions::System(LScriptArguments &Args) { if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } char *Exe = Args[0]->Str(); char *Arg = Args[1]->Str(); *Args.GetReturn() = LExecute(Exe, Arg); return true; } bool SystemFunctions::OsName(LScriptArguments &Args) { *Args.GetReturn() = LGetOsName(); return true; } bool SystemFunctions::OsVersion(LScriptArguments &Args) { LArray Ver; LGetOs(&Ver); Args.GetReturn()->SetList(); for (int i=0; i<3; i++) Args.GetReturn()->Value.Lst->Insert(new LVariant(Ver[i])); return true; } #define DefFn(Name) \ - GHostFunc(#Name, 0, (ScriptCmd)&SystemFunctions::Name) + LHostFunc(#Name, 0, (ScriptCmd)&SystemFunctions::Name) -GHostFunc SystemLibrary[] = +LHostFunc SystemLibrary[] = { // Debug DefFn(Assert), DefFn(Throw), DefFn(DebuggerEnabled), // String handling DefFn(LoadString), DefFn(FormatSize), DefFn(Sprintf), DefFn(Print), DefFn(ToString), // Containers/objects DefFn(New), DefFn(Delete), DefFn(Len), // Files DefFn(ReadTextFile), DefFn(WriteTextFile), DefFn(SelectFiles), DefFn(SelectFolder), DefFn(ListFiles), DefFn(DeleteFile), DefFn(CurrentScript), DefFn(PathJoin), DefFn(PathExists), DefFn(PathSep), // Time DefFn(ClockTick), DefFn(Sleep), DefFn(Now), // Images DefFn(CreateSurface), DefFn(ColourSpaceToString), DefFn(StringToColourSpace), // UI DefFn(MessageDlg), DefFn(GetInputDlg), DefFn(GetViewById), // System DefFn(Execute), DefFn(System), DefFn(OsName), DefFn(OsVersion), // End of list marker - GHostFunc(0, 0, 0), + LHostFunc(0, 0, 0), }; -GHostFunc *SystemFunctions::GetCommands() +LHostFunc *SystemFunctions::GetCommands() { return SystemLibrary; } diff --git a/src/common/Coding/ScriptVM.cpp b/src/common/Coding/ScriptVM.cpp --- a/src/common/Coding/ScriptVM.cpp +++ b/src/common/Coding/ScriptVM.cpp @@ -1,2169 +1,2168 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/Box.h" #include "lgi/common/TabView.h" #include "lgi/common/TextLog.h" #include "lgi/common/List.h" #include "lgi/common/ToolBar.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Matrix.h" #include "lgi/common/Menu.h" #include "ScriptingPriv.h" #define TIME_INSTRUCTIONS 0 #define POST_EXECUTE_STATE 0 // #define BREAK_POINT 0x0000009F #define ExitScriptExecution c.u8 = e #define SetScriptError c.u8 = e; Status = ScriptError #define CurrentScriptAddress (c.u8 - Base) #define CheckParam(ptr) if (!(ptr)) \ { \ OnException(_FL, CurrentScriptAddress-1, #ptr); \ c.u8 = e; \ Status = ScriptError; \ break; \ } #define AddLocalSize(NewSize) \ size_t LocalsBase = Locals.Length(); \ Locals.SetFixedLength(false); \ /* LgiTrace("%s:%i - Locals %i -> %i\n", _FL, LocalsBase, LocalsBase + NewSize); */ \ Locals.Length(LocalsBase + NewSize); \ Scope[SCOPE_LOCAL] = &Locals[LocalsBase]; \ Locals.SetFixedLength(); #ifdef WIN32 extern "C" uint64 __cdecl CallExtern64(void *FuncAddr, NativeInt *Ret, uint32_t Args, void *Arg); #elif defined(LINUX) #include #endif int LVariantCmp(LVariant *a, LVariant *b, NativeInt Data) { LVariant *Param = (LVariant*) Data; if (!a || !b) return 0; if (a->Type == GV_STRING && b->Type == GV_STRING) { const char *Empty = ""; const char *as = a->Str(); const char *bs = b->Str(); return _stricmp(as?as:Empty, bs?bs:Empty); } else if (a->Type == GV_DOM && b->Type == GV_DOM && Param) { const char *Fld = Param->Str(); int Dir = 1; if (Fld && *Fld == '-') { Fld++; Dir = -1; } LVariant av, bv; if (a->Value.Dom->GetValue(Fld, av) && b->Value.Dom->GetValue(Fld, bv)) { return LVariantCmp(&av, &bv, 0) * Dir; } } else if (a->Type == GV_INT32 && b->Type == GV_INT32) { return a->CastInt32() - b->CastInt32(); } else if (a->Type == GV_DATETIME && b->Type == GV_DATETIME) { return a->Value.Date->Compare(b->Value.Date); } else { LAssert(!"Impl a handler for this type."); } return 0; } inline LVariantType DecidePrecision(LVariantType a, LVariantType b) { if (a == GV_DOUBLE || b == GV_DOUBLE) return GV_DOUBLE; if (a == GV_INT64 || b == GV_INT64) return GV_INT64; return GV_INT32; } inline LVariantType ComparePrecision(LVariantType a, LVariantType b) { if (a == GV_NULL || b == GV_NULL) return GV_NULL; if (a == GV_DATETIME && b == GV_DATETIME) return GV_DATETIME; if (a == GV_DOUBLE || b == GV_DOUBLE) return GV_DOUBLE; if (a == GV_STRING || b == GV_STRING) return GV_STRING; if (a == GV_INT64 || b == GV_INT64) return GV_INT64; return GV_INT32; } inline char *CastString(LVariant *v, LVariant &cache) { if (v->Type == GV_STRING) return v->Str(); cache = *v; return cache.CastString(); } inline int CompareVariants(LVariant *a, LVariant *b) { // Calculates "a - b" switch (ComparePrecision(a->Type, b->Type)) { case GV_DATETIME: return a->Value.Date->Compare(b->Value.Date); break; case GV_DOUBLE: { double d = a->CastDouble() - b->CastDouble(); if (d < -MATRIX_DOUBLE_EPSILON) return -1; return d > MATRIX_DOUBLE_EPSILON; } case GV_STRING: { LVariant as, bs; char *A = CastString(a, as); char *B = CastString(b, bs); if (!A || !B) return -1; else return strcmp(A, B); break; } case GV_INT64: { int64 d = a->CastInt64() - b->CastInt64(); if (d < 0) return -1; return d > 0; } case GV_NULL: { // One or more values is NULL if (a->IsNull() && b->IsNull()) return 0; // The same.. LVariant *Val = a->IsNull() ? b : a; if (Val->IsNull()) { LAssert(0); return 0; } switch (Val->Type) { case GV_INT32: case GV_INT64: return Val->CastInt64() != 0; case GV_STRING: return Val->Str() != NULL; default: return Val->CastVoidPtr() != NULL; } break; } default: return a->CastInt32() - b->CastInt32(); break; } } LExecutionStatus LExternFunc::Call(LScriptContext *Ctx, LScriptArguments &Args) { if (!Lib || !Method) return ScriptError; LStream *Log = Ctx ? Ctx->GetLog() : NULL; if (Args.Length() != ArgType.Length()) { if (Log) Log->Print("Error: Extern '%s.%s' expecting %i arguments, not %i given.\n", Lib.Get(), Method.Get(), ArgType.Length(), Args.Length()); return ScriptError; } LArray Val; LArray Mem; bool UnsupportedArg = false; Val.Length(Args.Length() << 1); LPointer Ptr; Ptr.ni = &Val[0]; for (unsigned i=0; !UnsupportedArg && iCastVoidPtr(); *Ptr.vp++ = cp; } else { char *s = NewStr(v->CastString()); if (!s) { UnsupportedArg = true; break; } Mem.Add(s); *Ptr.vp++ = s; } break; } case GV_VOID_PTR: { *Ptr.vp++ = v->CastVoidPtr(); break; } default: { UnsupportedArg = true; break; } } } else { // Plain type switch (t.Base) { case GV_INT32: { #if defined(_WIN64) *Ptr.s64++ = v->CastInt32(); #else *Ptr.s32++ = v->CastInt32(); #endif break; } case GV_INT64: { *Ptr.s64++ = v->CastInt64(); break; } default: { UnsupportedArg = true; break; } } } } LLibrary Library(Lib); if (!Library.IsLoaded()) { if (Log) Log->Print("Error: Extern library '%s' missing.\n", Lib.Get()); return ScriptError; } void *c = Library.GetAddress(Method); if (!c) { if (Log) Log->Print("Error: Extern library '%s' has no method '%s'.\n", Lib.Get(), Method.Get()); return ScriptError; } #if defined(_MSC_VER) || (defined(MAC) && defined(LGI_32BIT) && !LGI_COCOA) ssize_t a = Ptr.ni - &Val[0]; #endif NativeInt r = 0; #if defined(_MSC_VER) #if defined(_WIN64) // 64bit... boooo no inline asm! void *b = &Val[0]; r = CallExtern64(c, &r, (uint32_t)a, b); #else // 32bit... yay inline asm! void *b = Ptr.ni - 1; _asm { mov ecx, a mov ebx, b } label1: _asm { push [ebx] sub ebx, 4 loop label1 mov ebx, c call ebx mov r, eax } #endif #elif defined(MAC) #if LGI_COCOA #warning FIXME #elif LGI_32BIT // 32bit only void *b = Ptr.ni - 1; asm ( "movl %2, %%ecx;" "movl %3, %%ebx;" "label1:" "pushl (%%ebx);" "subl %%ebx, 4;" "loop label1;" "call *%1;" :"=a"(r) /* output */ :"r"(c), "r"(a), "r"(b) /* input */ :/*"%eax",*/ "%ecx", "%ebx" /* clobbered register */ ); #endif #else // Not implemented, gcc??? LAssert(0); #endif *Args.GetReturn() = (int) r; for (unsigned i=0; iType == GV_BINARY && v->Value.Binary.Data != NULL && t.Base == GV_STRING) { // Cast the type back to t.Base char *p = (char*)v->Value.Binary.Data; v->Type = t.Base; v->Value.String = p; } } } Mem.DeleteArrays(); return ScriptSuccess; } struct CodeBlock { unsigned SrcLine; LArray AsmAddr; unsigned ViewLine; LAutoString Source; int SrcLines; LAutoString Asm; int AsmLines; }; class LVirtualMachinePriv : public LRefCount { LVariant ArrayTemp; char *CastArrayIndex(LVariant *Idx) { if (Idx == NULL || Idx->Type == GV_NULL) return NULL; if (Idx->Type == GV_STRING) return Idx->Str(); ArrayTemp = *Idx; return ArrayTemp.CastString(); } public: struct StackFrame { uint32_t CurrentFrameSize; - ssize_t PrevFrameStart; - size_t ReturnIp; - LVarRef ReturnValue; + ssize_t PrevFrameStart; + size_t ReturnIp; + LVarRef ReturnValue; }; enum RunType { RunContinue, RunStepInstruction, RunStepLine, RunStepOut }; - LStream *Log; - LCompiledCode *Code; - LExecutionStatus Status; - GPtr c; + LVirtualMachine *Vm; + LStream *Log = NULL; + LCompiledCode *Code = NULL; + LExecutionStatus Status = ScriptNotStarted; + LScriptPtr c; LVariant Reg[MAX_REGISTER]; LArray Locals; LVariant *Scope[SCOPE_MAX]; LArray Frames; RunType StepType; - LVmDebuggerCallback *DbgCallback; - bool DebuggerEnabled; - LVmDebugger *Debugger; - LVirtualMachine *Vm; - LScriptArguments *ArgsOutput; - bool BreakCpp; + LVmCallback *Callback = NULL; + LVmDebugger *Debugger = NULL; + LScriptArguments *ArgsOutput = NULL; LArray BreakPts; LString TempPath; + bool DebuggerEnabled = false; + bool BreakCpp = false; - LVirtualMachinePriv(LVirtualMachine *vm, LVmDebuggerCallback *Callback) + LVirtualMachinePriv(LVirtualMachine *vm, LVmCallback *callback) { Vm = vm; - DebuggerEnabled = true; - BreakCpp = false; - ArgsOutput = NULL; - Log = NULL; - Code = NULL; - Debugger = NULL; - DbgCallback = Callback; + Callback = callback; ZeroObj(Scope); } ~LVirtualMachinePriv() { } void DumpVariant(LStream *Log, LVariant &v) { if (!Log) return; switch (v.Type) { case GV_INT32: Log->Print("(int) %i", v.Value.Int); break; case GV_INT64: Log->Print("(int64) %I64i", v.Value.Int64); break; case GV_STRING: { char *nl = strchr(v.Value.String, '\n'); if (nl) Log->Print("(string) '%.*s...' (%i bytes)", nl - v.Value.String, v.Value.String, strlen(v.Value.String)); else Log->Print("(string) '%s'", v.Value.String); break; } case GV_DOUBLE: Log->Print("(double) %g", v.Value.Dbl); break; case GV_BOOL: Log->Print("(bool) %s", v.Value.Bool ? "true" : "false"); break; case GV_DOM: Log->Print("(LDom*) %p", v.Value.Dom); break; case GV_HASHTABLE: { Log->Print("(GHashTable*) %p {", v.Value.Hash); int n = 0; // const char *k; // for (LVariant *p = v.Value.Hash->First(&k); p; p = v.Value.Hash->Next(&k), n++) for (auto it : *v.Value.Hash) { Log->Print("%s\"%s\"=", n?",":"", it.key); DumpVariant(Log, *it.value); } Log->Print("}"); break; } case GV_LIST: { Log->Print("(LList*) %p {", v.Value.Lst); int n=0; for (auto i: *v.Value.Lst) { Log->Print("%s%i=", n?",":"", n); DumpVariant(Log, *i); n++; } Log->Print("}"); break; } case GV_NULL: { Log->Print("null"); break; } case GV_BINARY: { Log->Print("(Binary[%i])", v.Value.Binary.Length); if (v.Value.Binary.Data) { int i; for (i=0; i<16 && i < v.Value.Binary.Length; i++) Log->Print(" %.2x", ((uint8_t*)v.Value.Binary.Data)[i]); if (i < v.Value.Binary.Length) Log->Print("..."); } break; } default: Log->Print("(Type-%i) ????", v.Type); break; } } void DumpVariables(LVariant *v, int len) { if (!Log) return; for (int i=0; iPrint("[%i] = ", i); DumpVariant(Log, v[i]); Log->Print("\n"); } } } void OnException(const char *File, int Line, ssize_t Address, const char *Msg) { if (Address < 0) { uint8_t *Base = &Code->ByteCode[0]; Address = c.u8 - Base; } if (!File || Line < 0) { // Extract the file / line from the current script location File = Code->GetFileName(); Line = Code->ObjectToSourceAddress(Address); } if (Log) { char *Last = strrchr((char*)File, DIR_CHAR); Log->Print("%s Exception: %s (%s:%i)\n", Code->AddrToSourceRef(Address), Msg, Last?Last+1:File, Line); } else { LgiTrace("%s:%i - Exception @ %i: %s\n", File, Line, Address, Msg); } if (Vm && Vm->OpenDebugger(Code)) { if (!Debugger->GetCode()) { LAutoPtr Cp(new LCompiledCode(*Code)); Debugger->OwnCompiledCode(Cp); LStringPipe AsmBuf; Decompile(Code->UserContext, Code, &AsmBuf); LAutoString Asm(AsmBuf.NewStr()); Debugger->SetSource(Asm); } Debugger->OnAddress(Address); LString m; m.Printf("%s (%s:%i)", Msg, LGetLeaf(File), Line); Debugger->OnError(m); Debugger->Run(); } else { // Set the script return value to FALSE if (Frames.Length()) { StackFrame Sf = Frames[0]; LVarRef &Ret = Sf.ReturnValue; LVariant *RetVar = &Scope[Ret.Scope][Ret.Index]; *RetVar = false; } // Exit the script c.u8 = Code->ByteCode.AddressOf() + Code->ByteCode.Length(); // Set the script status... Status = ScriptError; } } LExecutionStatus Decompile(LScriptContext *Context, LCompiledCode *Code, LStream *log) { LExecutionStatus Status = ScriptSuccess; LAssert(sizeof(LVarRef) == 4); - GPtr c; + LScriptPtr c; uint8_t *Base = &Code->ByteCode[0]; c.u8 = Base; uint8_t *e = c.u8 + Code->ByteCode.Length(); LStream *OldLog = Log; if (log) Log = log; for (unsigned k=0; kGlobals.Length(); k++) { Log->Print("G%i = ", k); DumpVariant(Log, Code->Globals[k]); Log->Print("\n"); } Log->Print("\n"); LHashTbl, char*> Fn; for (unsigned m=0; mMethods.Length(); m++) { LFunctionInfo *Info = Code->Methods[m]; if (Info->ValidStartAddr()) Fn.Add(Info->GetStartAddr(), Info->GetName()); else LAssert(!"Method not defined."); } int OldLineNum = 0; while (c.u8 < e) { char *Meth = Fn.Find(CurrentScriptAddress); if (Meth) { Log->Print("%s:\n", Meth); } int LineNum = Code->ObjectToSourceAddress(CurrentScriptAddress); if (LineNum >= 0 && LineNum != OldLineNum) { Log->Print(" %i:\n", OldLineNum = LineNum); } switch (*c.u8++) { #define VM_DECOMP 1 #include "Instructions.h" #undef VM_DECOMP } } if (log) Log = OldLog; return Status; } LExecutionStatus Setup(LCompiledCode *code, uint32_t StartOffset, LStream *log, LFunctionInfo *Func, LScriptArguments *Args) { Status = ScriptSuccess; Code = code; if (!Code) return ScriptError; if (log) Log = log; else if (Code->SysContext && Code->SysContext->GetLog()) Log = Code->SysContext->GetLog(); else if (Code->UserContext && Code->UserContext->GetLog()) Log = Code->UserContext->GetLog(); // else LgiTrace("%s:%i - Execution without a log?\n", _FL); LAssert(sizeof(LVarRef) == 4); uint8_t *Base = c.u8 = &Code->ByteCode[0]; uint8_t *e = c.u8 + Code->ByteCode.Length(); Scope[SCOPE_REGISTER] = Reg; Scope[SCOPE_LOCAL] = NULL; Scope[SCOPE_GLOBAL] = &Code->Globals[0]; Scope[SCOPE_OBJECT] = NULL; Scope[SCOPE_RETURN] = Args->GetReturn(); #ifdef _DEBUG const char *SourceFileName = Code->GetFileName(); char Obj[MAX_PATH_LEN]; if (SourceFileName) { if (strchr(SourceFileName, DIR_CHAR)) strcpy_s(Obj, sizeof(Obj), SourceFileName); else if (TempPath != NULL) LMakePath(Obj, sizeof(Obj), TempPath, SourceFileName); else { LGetSystemPath(LSP_TEMP, Obj, sizeof(Obj)); LMakePath(Obj, sizeof(Obj), Obj, SourceFileName); } char *Ext = LGetExtension(Obj); if (Ext) strcpy_s(Ext, sizeof(Obj)-(Ext-Obj), "asm"); else strcat_s(Obj, sizeof(Obj), ".asm"); } else { LAutoString DataPath; if (Code->UserContext) DataPath = Code->UserContext->GetDataFolder(); if (!DataPath) { char p[256]; if (LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p))) DataPath.Reset(NewStr(p)); } LMakePath(Obj, sizeof(Obj), DataPath, "Script.asm"); } { LDirectory SrcD, ObjD; bool OutOfDate = true; if (LFileExists(SourceFileName) && SrcD.First(SourceFileName, NULL) != 0 && ObjD.First(Obj, NULL) != 0) { OutOfDate = ObjD.GetLastWriteTime() < SrcD.GetLastWriteTime(); } if (OutOfDate || Debugger) { LFile f; LStringPipe p; LStream *Out = NULL; if (Debugger) { Out = &p; } else if (f.Open(Obj, O_WRITE)) { f.SetSize(0); Out = &f; } if (Out) { LExecutionStatus Decomp = Decompile(Code->UserContext, Code, Out); f.Close(); if (Decomp != ScriptSuccess) { LAssert(!"Decompilation failed."); return ScriptError; } if (Debugger) { LAutoString a(p.NewStr()); Debugger->OnAddress(CurrentScriptAddress); Debugger->SetSource(a); } } } } #endif #if TIME_INSTRUCTIONS LARGE_INTEGER freq = {0}, start, end; QueryPerformanceFrequency(&freq); LHashTbl, int64> Timings; LHashTbl, int> TimingFreq; #endif // Calling a function only, not the whole script StackFrame &Sf = Frames.New(); Sf.ReturnIp = e - c.u8; Sf.PrevFrameStart = 0; Sf.ReturnValue.Scope = SCOPE_RETURN; Sf.ReturnValue.Index = 0; // array is only one item long anyway if (Func) { // Set up stack for function call if (!Func->ValidFrameSize()) { Log->Print("%s:%i - Function '%s' has an invalid frame size. (Script: %s).\n", _FL, Func->GetName(), Code->AddrToSourceRef(Func->GetStartAddr())); return ScriptError; } Sf.CurrentFrameSize = Func->GetFrameSize(); AddLocalSize(Sf.CurrentFrameSize); if (Args) { // Check the local frame size is at least big enough for the args... if (Args->Length() > Sf.CurrentFrameSize) { Log->Print("%s:%i - Arg count mismatch, Supplied: %i, FrameSize: %i (Script: %s).\n", _FL, (int)Args->Length(), (int)Sf.CurrentFrameSize, Code->AddrToSourceRef(Func->GetStartAddr())); return ScriptError; } // Put the arguments of the function call into the local array for (unsigned i=0; iLength(); i++) { Locals[LocalsBase+i] = *(*Args)[i]; } } if (!Func->ValidStartAddr()) { Log->Print("%s:%i - Function '%s' is not defined. (Script: %s).\n", _FL, Func->GetName(), Code->AddrToSourceRef(Func->GetStartAddr())); return ScriptError; } // Set IP to start of function c.u8 = Base + Func->GetStartAddr(); } else { // Executing body of script Sf.CurrentFrameSize = 0; if (StartOffset > 0) c.u8 = Base + StartOffset; } return Status; } int NearestLine(size_t Addr) { int l = Code->Debug.Find(Addr); if (l >= 0) return l; for (int Off = 1; Off < 20; Off++) { int l = Code->Debug.Find(Addr + Off); if (l >= 0) { return l; } l = Code->Debug.Find(Addr - Off); if (l >= 0) { return l; } } return -1; } LExecutionStatus Run(RunType Type) { LAssert(Code != NULL); uint8_t *Base = &Code->ByteCode[0]; uint8_t *e = Base + Code->ByteCode.Length(); if (Type == RunContinue && BreakPts.Length() == 0) { // Unconstrained execution while (c.u8 < e) { #if TIME_INSTRUCTIONS uint8 TimedOpCode = *c.u8; QueryPerformanceCounter(&start); #endif #ifdef BREAK_POINT if (c.u8 - Base == BREAK_POINT) { int asd=0; } #endif switch (*c.u8++) { #define VM_EXECUTE 1 #include "Instructions.h" #undef VM_EXECUTE } #if TIME_INSTRUCTIONS QueryPerformanceCounter(&end); int Ticks = end.QuadPart - start.QuadPart; int64 i = Timings.Find(TimedOpCode); Timings.Add(TimedOpCode, i + Ticks); i = TimingFreq.Find(TimedOpCode); TimingFreq.Add(TimedOpCode, i + 1); #endif } if (Log) { #if TIME_INSTRUCTIONS Log->Print("\nTimings:\n"); Log->Print("%-20s%-10s%-10s%-10s\n", "Instr", "Total", "Freq", "Ave"); int Op; for (int64 t=Timings.First(&Op); t; t=Timings.Next(&Op)) { int Frq = TimingFreq.Find(Op); int MilliSec = t * 1000000 / freq.QuadPart; Log->Print("%-20s%-10i%-10i%-10i\n", InstToString((GInstruction)Op), MilliSec, Frq, MilliSec / Frq); } Log->Print("\n"); #endif #if POST_EXECUTE_STATE Log->Print("Stack:\n"); char *v; for (void *i=Code->Globals.Lut.First(&v); i; i=Code->Globals.Lut.Next(&v)) { int Idx = (int)i - 1; if (Idx >= 0 && Idx < Code->Globals.Length()) { Log->Print("%s = ", v); DumpVariant(Log, Code->Globals[Idx]); Log->Print("\n"); } } Log->Print("\nRegisters:\n"); DumpVariables(Reg, MAX_REGISTER); #endif } } else { // Stepping through code // LHashTbl, int> &Debug = Code->Debug; int Param = 0; switch (Type) { case RunStepLine: Param = NearestLine(CurrentScriptAddress); break; case RunStepOut: Param = (int)Frames.Length(); break; default: break; } if (BreakCpp) #if defined(WIN32) && !defined(_WIN64) _asm int 3 #else assert(!"BreakPoint"); #endif while (c.u8 < e) { if (Type == RunContinue && BreakPts.HasItem(c.u8 - Base)) break; switch (*c.u8++) { #define VM_EXECUTE 1 #include "Instructions.h" #undef VM_EXECUTE } if (Type == RunStepLine) { int CurLine = NearestLine(CurrentScriptAddress); if (CurLine && CurLine != Param) break; } else if (Type == RunStepOut) { if ((int)Frames.Length() < Param) break; } else if (Type == RunStepInstruction) break; } } if (Debugger && Status != ScriptError) Debugger->OnAddress(CurrentScriptAddress); return Status; } }; bool LVirtualMachine::BreakOnWarning = false; -LVirtualMachine::LVirtualMachine(LVmDebuggerCallback *callback) +LVirtualMachine::LVirtualMachine(LVmCallback *callback) { d = new LVirtualMachinePriv(this, callback); d->IncRef(); } LVirtualMachine::LVirtualMachine(LVirtualMachine *vm) { d = vm->d; d->IncRef(); } LVirtualMachine::~LVirtualMachine() { if (d->Vm == this) d->Vm = NULL; d->DecRef(); } LExecutionStatus LVirtualMachine::Execute(LCompiledCode *Code, uint32_t StartOffset, LStream *Log, bool StartImmediately, LVariant *Return) { if (!Code) return ScriptError; LScriptArguments Args(this, Return); LExecutionStatus s = d->Setup(Code, StartOffset, Log, NULL, &Args); if (s != ScriptSuccess || !StartImmediately) return s; return d->Run(LVirtualMachinePriv::RunContinue); } LExecutionStatus LVirtualMachine::ExecuteFunction(LCompiledCode *Code, LFunctionInfo *Func, LScriptArguments &Args, LStream *Log, LScriptArguments *ArgsOut) { LCompiledCode *Cc = dynamic_cast(Code); if (!Cc || !Func) return ScriptError; LExecutionStatus s = d->Setup(Cc, 0, Log, Func, &Args); if (s != ScriptSuccess) return s; d->ArgsOutput = ArgsOut; Args.Vm = this; LExecutionStatus r = d->Run(LVirtualMachinePriv::RunContinue); Args.Vm = NULL; return r; } void LVirtualMachine::SetDebuggerEnabled(bool b) { d->DebuggerEnabled = b; } LVmDebugger *LVirtualMachine::OpenDebugger(LCompiledCode *Code, const char *Assembly) { if (d->DebuggerEnabled && !d->Debugger) { - if (!d->DbgCallback) + if (!d->Callback) return NULL; - d->Debugger = d->DbgCallback->AttachVm(this, Code, Assembly); + d->Debugger = d->Callback->AttachVm(this, Code, Assembly); } return d->Debugger; } bool LVirtualMachine::StepInstruction() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunStepInstruction); return s != ScriptError; } bool LVirtualMachine::StepLine() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunStepLine); return s != ScriptError; } bool LVirtualMachine::StepOut() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunStepOut); return s != ScriptError; } bool LVirtualMachine::BreakExecution() { return false; } bool LVirtualMachine::Continue() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunContinue); return s != ScriptError; } bool LVirtualMachine::BreakPoint(const char *File, int Line, bool Add) { return false; } bool LVirtualMachine::BreakPoint(int Addr, bool Add) { if (Add) d->BreakPts.Add(Addr); else d->BreakPts.Delete(Addr); return true; } void LVirtualMachine::SetBreakCpp(bool Brk) { d->BreakCpp = Brk; } +LVmCallback *LVirtualMachine::GetCallback() +{ + return d->Callback; +} + void LVirtualMachine::SetTempPath(const char *Path) { d->TempPath = Path; } //////////////////////////////////////////////////////////////////// /* bool GTypeDef::GetVariant(const char *Name, LVariant &Value, char *Arr) { GMember *m = Members.Find(Name); if (!m || !Object) { LAssert(!"No member?"); return false; } GPtr p; p.i8 = Object; p.i8 += m->Offset; switch (m->Type) { case GV_INT32: { Value = *p.i32; break; } case GV_DOUBLE: { Value = *p.dbl; break; } case GV_STRING: { Value = p.i8; break; } case GV_CUSTOM: { Value.Empty(); Value.Type = GV_CUSTOM; Value.Value.Custom.Dom = m->Nest; Value.Value.Custom.Data = p.i8; break; } default: { return false; } } return true; } bool GTypeDef::SetVariant(const char *Name, LVariant &Value, char *Arr) { GMember *m = Members.Find(Name); if (!m || !Object) { LAssert(!"No member?"); return false; } GPtr p; p.i8 = Object; p.i8 += m->Offset; switch (m->Type) { case GV_INT32: { *p.i32 = Value.CastInt32(); break; } case GV_DOUBLE: { *p.dbl = Value.CastDouble(); break; } case GV_STRING: { char *s = Value.CastString(); if (!s) return false; int i; for (i = 0; *s && i < m->Size - 1; i++) { *p.i8++ = *s++; } if (i < m->Size - 1) *p.i8 = 0; break; } case GV_CUSTOM: { GTypeDef *t = dynamic_cast(Value.Value.Custom.Dom); if (m->Nest == t) { memcpy(p.i8, Value.Value.Custom.Data, t->Sizeof()); } break; } default: { return false; } } return true; } */ /////////////////////////////////////////////////////////////////////////////////////////////////////////////// uint32_t IconsData[] = { 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9D9CCEBE, 0x3B166419, 0x74594357, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x543CF81F, 0xCEDE647C, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7C998D1B, 0xF81FB61C, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCEBFF81F, 0x43DB4C1C, 0xDF1E955B, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8C0CF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D5CF81F, 0x43595C1A, 0x3AF74338, 0x8CFA4B57, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xC69D6C39, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0x647BADFD, 0x543C53FB, 0x3B1553FB, 0x329132B2, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F64CB, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0x8D9F8D9F, 0x855E857E, 0x7CFD7D1D, 0x74BC74DC, 0xF81F74DC, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8BEBF81F, 0xF81F83AB, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7CBB8D5C, 0xF81FB63D, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F5BD8, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x959D647C, 0xCEBDF81F, 0x32913AD3, 0xB61B5353, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x3BA564CB, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0x74DC8D9F, 0x8D9FF81F, 0x74DC8D9F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9F8D9F, 0x855E857E, 0x7CFD7D1D, 0x74BC74DC, 0xF81F74DC, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0xAE1EB65E, 0xD71FA5FE, 0x853D8D7D, 0x6CBD7CFD, 0xF81F2AB5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8BEBF81F, 0x7329FF98, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE9E5C1A, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F5398, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F6C7C, 0x5BD6F81F, 0xD6DD5BB5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB6D45CAA, 0xF81F3BA5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0x2AB5D71F, 0x8D9FF81F, 0x2AB5D71F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xD71F8D9F, 0xC6DFD71F, 0xB65FBE7F, 0x9DDEAE3F, 0xF81F2AB5, 0xF81FF81F, 0xF81FF81F, 0x857EF81F, 0x9DDEAE1E, 0xFFFFDF3F, 0x74FD853D, 0x647C6CBC, 0xF81F2274, 0xF81FF81F, 0xF81FF81F, 0x9C6CF81F, 0x944D944C, 0x944D8C0C, 0xFF54FF76, 0xF81F62A8, 0xF81FF81F, 0xF81FF81F, 0xBE5D4B99, 0x543CF81F, 0xC6BE5C7C, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F5398, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F53DB, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x75ED5489, 0x3BA5B6D4, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x857EF81F, 0x2274DF3F, 0x857EF81F, 0x2274DF3F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xDF3F857E, 0xB65FCEDF, 0x9DBEAE1F, 0x851B959E, 0xF81F2274, 0xF81FF81F, 0xF81FF81F, 0x855EF81F, 0xE75F9DDE, 0xFFFFFFFF, 0x6CBC74DD, 0x543C647C, 0xF81F1A33, 0xF81FF81F, 0xF81FF81F, 0x944BF81F, 0xFFD9F756, 0xFF53FF97, 0xFEEEFF31, 0x5226F66A, 0xF81FF81F, 0xF81FF81F, 0x84DA6C3A, 0xBE7EADDC, 0x43DB4C1C, 0xF81F953B, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F4B77, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0x9D7C5C3B, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x75ED4C48, 0x5D0A75ED, 0xF81F3BA5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x855EF81F, 0x1A33D71F, 0x855EF81F, 0x1A33D71F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xD71F855E, 0xA5FFBE7F, 0x855E959E, 0x6C7A7CFD, 0xF81F1A33, 0xF81FF81F, 0xF81FF81F, 0x7D1DF81F, 0xFFFFDF1E, 0xFFFFFFFF, 0xFFFFFFFF, 0x4BFCC69D, 0xF81F11F2, 0xF81FF81F, 0xF81FF81F, 0x940AF81F, 0xFF75FF97, 0xFEEEFF31, 0xF6ABFECD, 0xBCA7E5E8, 0xF81F49C5, 0xF81FF81F, 0x7CBAB61C, 0x541C53FA, 0x3B1553FB, 0x329132B2, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F4B57, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x6C9CB63D, 0x43584BBA, 0x3AD53B16, 0x743742F4, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x6D8B4407, 0x3C055D0A, 0x1B212B84, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7D1DF81F, 0x11F2CEBF, 0x7D1DF81F, 0x11F2CEBF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCEBF7D1D, 0x9DBEAE3F, 0x7CFD8D5E, 0x53B76CBC, 0xF81F11F2, 0xF81FF81F, 0xF81FF81F, 0x7CFDF81F, 0xCEBD853D, 0xFFFFFFFF, 0x541C5C5C, 0x43BBFFFF, 0xF81F09B1, 0xF81FF81F, 0xF81FF81F, 0x83A9F81F, 0xFF31FF74, 0xF6ACF6CF, 0xDDEAF68B, 0x41A4B467, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xC69DF81F, 0x32913AD3, 0xB5FB4B53, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F4336, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5D0A33E5, 0x2B843C05, 0xF81F0B00, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7CFDF81F, 0x09B1BE9F, 0x7CFDF81F, 0x09B1BE9F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xBE9F7CFD, 0x959EA5FF, 0x6C9C7CFD, 0x4B565C3B, 0xF81F09B1, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x6CBC74FD, 0xFFFFBE3B, 0x43DC541C, 0x337BFFFF, 0xF81F0990, 0xF81FF81F, 0xF81FF81F, 0x8389F81F, 0x6AA472E7, 0x72C56264, 0xB487DDA9, 0xF81F41A4, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5BD6F81F, 0xF81F5BB5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F4336, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x3C052BA4, 0x0B002B84, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x0990AE5F, 0x74DCF81F, 0x0990AE5F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xAE5F74DC, 0x7D1D95BE, 0x5C3B6CBC, 0x43354BD9, 0xF81F0990, 0xF81FF81F, 0xF81FF81F, 0x74BCF81F, 0x647C6CBC, 0x9D7A543C, 0x3B9B43DB, 0x2B5BFFFF, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5A45F81F, 0x41A4AC26, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F5377, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x2B842363, 0xF81F0B00, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x74BCF81F, 0x0170A5FE, 0x74BCF81F, 0x0170A5FE, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xA5FE74BC, 0x6C79851A, 0x4B555BF8, 0x32D34314, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x543C5C7C, 0x43BB4BFC, 0x335B3B9B, 0x231BFFFF, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x49E4F81F, 0xF81F41A4, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9D7A53B7, 0x4BBAF81F, 0xB61C6459, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0B001B42, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x01700170, 0x74DCF81F, 0x01700170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x3B1674DC, 0x2A9432F6, 0x11F22253, 0x017009B1, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x3B163B16, 0x2A9432F6, 0x11F22253, 0x017009B1, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x41A4F81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0x747863F7, 0x953BB61C, 0x4BB843B9, 0xB61B7C98, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F1301, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x6C18ADBA, 0x53D953B7, 0x3B144B98, 0x32503291, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB61BF81F, 0x32503291, 0xA5794B12, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5B95F81F, 0xB61A5373, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, }; LInlineBmp DbgIcons = {128, 16, 16, IconsData}; enum DbgCtrls { IDC_STATIC = -1, IDC_TABS = 300, IDC_BOX, IDC_BOX2, IDC_TEXT, IDC_LOCALS, IDC_GLOBALS, IDC_REGISTERS, IDC_STACK, IDC_LOG, IDC_RUN, IDC_PAUSE, IDC_STOP, IDC_RESTART, IDC_GOTO, IDC_STEP_INSTR, IDC_STEP_LINE, IDC_STEP_OUT, IDC_SOURCE_LST, IDC_BREAK_POINT, IDC_BREAK_CPP, IDC_VARS_TBL }; struct LScriptVmDebuggerPriv; class LDebugView : public LTextView3 { LScriptVmDebuggerPriv *d; int CurLine; int ErrorLine; LString Error; LArray BreakPts; public: LDebugView(LScriptVmDebuggerPriv *priv); ~LDebugView(); void SetError(const char *Err); int GetCurLine() { return CurLine; } int GetAddr(); void ScrollToCurLine(); void PourText(size_t Start, ssize_t Length) override; void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) override; void OnPaint(LSurface *pDC) override; bool Breakpoint(int Addr); }; struct LScriptVmDebuggerPriv { // Current script bool OwnVm; LAutoPtr Vm; - LVmDebuggerCallback *Callback; + LVmCallback *Callback; LString Script, Assembly; LArray Blocks; size_t CurrentAddr; LArray LineIsAsm; LAutoPtr Obj; LVariant Return; bool AcceptNotify; // Ui bool RunLoop; LView *Parent; LBox *Main; LBox *Sub; LList *SourceLst; LTabView *Tabs; LDebugView *Text; LList *Locals, *Globals, *Registers, *Stack; LTextLog *Log; LToolBar *Tools; LTableLayout *VarsTbl; LScriptVmDebuggerPriv() { RunLoop = false; OwnVm = false; CurrentAddr = -1; Main = NULL; Tabs = NULL; Log = NULL; Text = NULL; Locals = NULL; Globals = NULL; Registers = NULL; Stack = NULL; Tools = NULL; SourceLst = NULL; Callback = NULL; VarsTbl = NULL; } }; LDebugView::LDebugView(LScriptVmDebuggerPriv *priv) : LTextView3(IDC_TEXT, 0, 0, 100, 100) { d = priv; ErrorLine = -1; SetWrapType(TEXTED_WRAP_NONE); GetCss(true)->PaddingLeft(LCss::Len(LCss::LenPx, 18)); } LDebugView::~LDebugView() { } void LDebugView::SetError(const char *Err) { ErrorLine = CurLine; Error = Err; } #define IsHexChar(c) \ ( \ IsDigit(c) \ || \ ((c) >= 'a' && (c) <= 'f') \ || \ ((c) >= 'A' && (c) <= 'F') \ ) int IsAddr(char16 *Ln) { int Addr = 0; for (char16 *s = Ln; *s && *s != '\n' && s < Ln + 8; s++) { Addr += IsHexChar(*s); } if (Addr != 8) return -1; return HtoiW(Ln); } int LDebugView::GetAddr() { ssize_t Index; LTextLine *t = GetTextLine(Cursor, &Index); if (!t) return -1; int Addr = IsAddr(Text + t->Start); return Addr; } void LDebugView::ScrollToCurLine() { SetLine(CurLine); } bool LDebugView::Breakpoint(int Addr) { if (BreakPts.HasItem(Addr)) { BreakPts.Delete(Addr); Invalidate(); return false; } else { BreakPts.Add(Addr); Invalidate(); return true; } } void LDebugView::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { LTextView3::OnPaintLeftMargin(pDC, r, colour); pDC->Colour(LColour(192, 0, 0)); LFont *f = GetFont(); f->Colour(L_LOW, L_WORKSPACE); f->Transparent(true); int Fy = f->GetHeight(); int Start = VScroll ? (int)VScroll->Value() : 0; int Page = (r.Y() + Fy - 1) / Fy; int Ln = Start; int Rad = (Fy >> 1) - 1; int PadY = GetCss(true)->PaddingTop().ToPx(Y(), f) + ((Fy - Rad) >> 1); auto It = Line.begin(Start); for (auto i = *It; i && Ln <= Start + Page; i = *(++It), Ln++) { int OffY = (Ln - Start) * f->GetHeight(); /* LString Num; Num.Printf("%i", Ln); LDisplayString Ds(f, Num); Ds.Draw(pDC, 0, r.y1+OffY); */ char16 *s = Text + i->Start; int Addr = IsAddr(s); if (BreakPts.HasItem(Addr)) { pDC->FilledCircle(r.x1 + Rad + 2, OffY + PadY + Rad, Rad); } } f->Transparent(false); f->Colour(L_TEXT, L_WORKSPACE); } void LDebugView::OnPaint(LSurface *pDC) { LTextView3::OnPaint(pDC); if (Error) { LTextLine *Ln = Line[ErrorLine]; LFont *f = GetFont(); LRect c = GetClient(); int Pad = 3; LDisplayString Ds(f, Error); LRect r(0, 0, Ds.X()-1, Ds.Y()-1); r.Inset(-Pad, -Pad); r.Offset(c.X()-r.X(), Ln ? Ln->r.y1 - ScrollYPixel(): 0); f->Transparent(false); f->Colour(LColour::White, LColour::Red); Ds.Draw(pDC, r.x1 + Pad, r.y1 + Pad, &r); } } void LDebugView::PourText(size_t Start, ssize_t Len) { LTextView3::PourText(Start, Len); CurLine = -1; for (unsigned i=0; iBlocks.Length(); i++) { CodeBlock &b = d->Blocks[i]; for (unsigned n=0; nCurrentAddr >= b.AsmAddr[n]) { CurLine = b.ViewLine + b.SrcLines + n - 1; } } } unsigned Idx = 0; for (auto l: Line) { // char16 *t = Text + l->Start; // char16 *e = t + l->Len; if (CurLine == Idx) { l->c.Rgb(0, 0, 0); l->Back = LColour(L_DEBUG_CURRENT_LINE); } else { bool IsAsm = Idx < d->LineIsAsm.Length() ? d->LineIsAsm[Idx] : false; if (IsAsm) { l->c.Rgb(0, 0, 255); l->Back.Rgb(0xf0, 0xf0, 0xf0); } } Idx++; } } -LVmDebuggerWnd::LVmDebuggerWnd(LView *Parent, LVmDebuggerCallback *Callback, LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) +LVmDebuggerWnd::LVmDebuggerWnd(LView *Parent, LVmCallback *Callback, LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) { d = new LScriptVmDebuggerPriv; d->Parent = Parent; d->AcceptNotify = false; if (Vm) d->Vm.Reset(new LVirtualMachine(Vm)); d->Callback = Callback; if (Code) d->Script = Code->GetSource(); d->Assembly = Assembly; LRect r(0, 0, 1000, 900); SetPos(r); if (Parent) MoveSameScreen(Parent); else MoveToCenter(); Name("Script Debugger"); if (Attach(NULL)) { if ((Menu = new LMenu)) { Menu->Attach(this); LSubMenu *s = Menu->AppendSub("Debug"); s->AppendItem("Run", IDC_RUN, true, -1, "F5"); s->AppendItem("Pause", IDC_PAUSE, true, -1, NULL); s->AppendItem("Stop", IDC_STOP, true, -1, "Ctrl+Break"); s->AppendItem("Restart", IDC_RESTART, true, -1, NULL); s->AppendItem("Goto", IDC_GOTO, true, -1, NULL); s->AppendSeparator(); s->AppendItem("Step Instruction", IDC_STEP_INSTR, true, -1, "F11"); s->AppendItem("Step Line", IDC_STEP_LINE, true, -1, "F10"); s->AppendItem("Step Out", IDC_STEP_OUT, true, -1, "Shift+F11"); s->AppendSeparator(); s->AppendItem("Breakpoint", IDC_BREAK_POINT, true, -1, "F9"); s->AppendItem("Break Into C++", IDC_BREAK_CPP, true, -1, "Ctrl+F9"); } AddView(d->Tools = new LToolBar); uint16 *Px = (uint16*) DbgIcons.Data; LImageList *il = new LImageList(16, 16, DbgIcons.Create(*Px)); if (il) d->Tools->SetImageList(il, 16, 16, true); d->Tools->AppendButton("Run", IDC_RUN); d->Tools->AppendButton("Pause", IDC_PAUSE); d->Tools->AppendButton("Stop", IDC_STOP); d->Tools->AppendButton("Restart", IDC_RESTART); d->Tools->AppendButton("Goto", IDC_GOTO); d->Tools->AppendSeparator(); d->Tools->AppendButton("Step Instruction", IDC_STEP_INSTR); d->Tools->AppendButton("Step Line", IDC_STEP_LINE); d->Tools->AppendButton("Step Out", IDC_STEP_OUT); AddView(d->Main = new LBox(IDC_BOX)); d->Main->SetVertical(true); d->Main->AddView(d->Sub = new LBox(IDC_BOX2)); d->Sub->SetVertical(false); d->Sub->AddView(d->SourceLst = new LList(IDC_SOURCE_LST, 0, 0, 100, 100)); d->SourceLst->GetCss(true)->Width(LCss::Len("200px")); d->SourceLst->AddColumn("Source", 200); d->Sub->AddView(d->Text = new LDebugView(d)); d->Main->AddView(d->Tabs = new LTabView(IDC_TABS)); d->Tabs->GetCss(true)->Height(LCss::Len("250px")); LTabPage *p = d->Tabs->Append("Variables"); p->Append(d->VarsTbl = new LTableLayout(IDC_VARS_TBL)); int x = 0, y = 0; auto *c = d->VarsTbl->GetCell(x++, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Globals:")); c = d->VarsTbl->GetCell(x++, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Locals:")); c = d->VarsTbl->GetCell(x++, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Registers:")); x = 0; y++; c = d->VarsTbl->GetCell(x++, y); c->Add(d->Globals = new LList(IDC_GLOBALS, 0, 0, 100, 100)); d->Globals->AddColumn("Name",100); d->Globals->AddColumn("Value",400); c = d->VarsTbl->GetCell(x++, y); c->Add(d->Locals = new LList(IDC_LOCALS, 0, 0, 100, 100)); d->Locals->AddColumn("Name",100); d->Locals->AddColumn("Value",400); c = d->VarsTbl->GetCell(x++, y); c->Add(d->Registers = new LList(IDC_REGISTERS, 0, 0, 100, 100)); d->Registers->AddColumn("Name",100); d->Registers->AddColumn("Value",400); p = d->Tabs->Append("Stack"); p->Append(d->Stack = new LList(IDC_STACK, 0, 0, 100, 100)); d->Stack->SetPourLargest(true); d->Stack->AddColumn("Address", 100); d->Stack->AddColumn("Function", 300); p = d->Tabs->Append("Log"); p->Append(d->Log = new LTextLog(IDC_LOG)); AttachChildren(); Visible(true); { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), LGetExePath(), "../Scripts"); LDirectory dir; LListItem *Match = NULL; d->SourceLst->MultiSelect(false); for (int b = dir.First(p); b; b = dir.Next()) { if (!dir.IsDir()) { char *n = dir.GetName(); if (stristr(n, ".script") && dir.Path(p, sizeof(p))) { LListItem *it = new LListItem; it->SetText(dir.GetName(), 0); it->SetText(p, 1); if (Code && Code->GetFileName()) { if (_stricmp(p, Code->GetFileName()) == 0) Match = it; } d->SourceLst->Insert(it); } } } if (!Match && Code) { LListItem *it = new LListItem; if (it) { it->SetText(LGetLeaf(Code->GetFileName()), 0); it->SetText(Code->GetFileName(), 1); d->SourceLst->Insert(it); it->Select(true); } } } } d->AcceptNotify = true; } LVmDebuggerWnd::~LVmDebuggerWnd() { LAssert(d->RunLoop == false); } bool LVmDebuggerWnd::OnRequestClose(bool OsShuttingDown) { if (!d->RunLoop) return LWindow::OnRequestClose(OsShuttingDown); d->RunLoop = false; return false; // Wait for Run() to exit in it's own time. } void LVmDebuggerWnd::Run() { // This is to allow objects on the application's stack to // still be valid while the debugger UI is shown. d->RunLoop = true; while (d->RunLoop && Visible()) { LYield(); LSleep(20); } Quit(); } LStream *LVmDebuggerWnd::GetLog() { return d->Log; } void LVmDebuggerWnd::OwnVm(bool Own) { d->OwnVm = Own; } void LVmDebuggerWnd::OwnCompiledCode(LAutoPtr Cc) { d->Obj = Cc; } LCompiledCode *LVmDebuggerWnd::GetCode() { return d->Obj; } void LVmDebuggerWnd::SetSource(const char *Mixed) { #if 1 LStringPipe Glob(256); LStringPipe Tmp(256); d->Blocks.Length(0); CodeBlock *Cur = &d->Blocks.New(); // Parse the mixed source auto t = LString(Mixed).SplitDelimit("\n", -1, false); bool InGlobals = true; int InAsm = -1; for (unsigned i=0; i Code Cur->Asm.Reset(Tmp.NewStr()); Cur = &d->Blocks.New(); } else { // Code -> Asm Tmp.Empty(); } InAsm = IsAsm; } Tmp.Print("%s\n", l); if (InAsm) { Cur->AsmLines++; Cur->AsmAddr.Add(htoi(l)); } else if (!Cur->SrcLine) { while (*l == ' ') l++; if (IsDigit(*l)) Cur->SrcLine = atoi(l); } } if (InAsm) Cur->Asm.Reset(Tmp.NewStr()); Tmp.Empty(); LStringPipe Txt; auto Src = d->Script.SplitDelimit("\n", -1, false); unsigned SrcLine = 1; unsigned ViewLine = 1; for (unsigned i=0; iBlocks.Length(); i++) { CodeBlock &b = d->Blocks[i]; if (b.SrcLine > 0) { while (SrcLine <= b.SrcLine) { char *s = Src[SrcLine-1]; Tmp.Print("%i: %s\n", SrcLine, s ? s : ""); b.SrcLines++; SrcLine++; } b.Source.Reset(Tmp.NewStr()); } if (b.Source && b.Asm) { b.ViewLine = ViewLine; ViewLine += b.SrcLines + b.AsmLines; Txt.Print("%s%s", b.Source.Get(), b.Asm.Get()); } else if (b.Source) { b.ViewLine = ViewLine; ViewLine += b.SrcLines; Txt.Print("%s", b.Source.Get()); } else if (b.Asm) { b.ViewLine = ViewLine; ViewLine += b.AsmLines; Txt.Print("%s", b.Asm.Get()); } } while (SrcLine <= Src.Length()) { Txt.Print("%i: %s\n", SrcLine, Src[SrcLine-1].Get()); SrcLine++; } for (unsigned i=0; iBlocks.Length(); i++) { CodeBlock &b = d->Blocks[i]; int Base = b.ViewLine + b.SrcLines; for (int n = Base; nLineIsAsm[n-1] = true; } LAutoString a(Txt.NewStr()); d->Text->Name(a); #else d->Text->Name(Mixed); #endif } void LVmDebuggerWnd::UpdateVariables(LList *Lst, LVariant *Arr, ssize_t Len, char Prefix) { if (!d->Vm || !Lst || !Arr) return; List all; Lst->GetAll(all); LListItem *it; for (ssize_t i=0; iVm->d->DumpVariant(&p, *v); LAutoString a(p.NewStr()); char nm[32]; sprintf_s(nm, sizeof(nm), "%c" LPrintfSSizeT, Prefix, i); if (i >= (ssize_t)all.Length()) { it = new LListItem; all.Insert(it); Lst->Insert(it); } it = i < (ssize_t)all.Length() ? all[i] : NULL; if (it) { it->SetText(nm, 0); it->SetText(a, 1); } } Lst->ResizeColumnsToContent(); } void LVmDebuggerWnd::OnAddress(size_t Addr) { d->CurrentAddr = Addr; if (d->Text) { ssize_t Sz = d->Text->Length(); d->Text->PourText(0, Sz); d->Text->ScrollToCurLine(); d->Text->Invalidate(); } OnNotify(d->Tabs, LNotifyValueChanged); } void LVmDebuggerWnd::OnError(const char *Msg) { if (Msg) d->Text->SetError(Msg); } void LVmDebuggerWnd::OnRun(bool Running) { } void LVmDebuggerWnd::LoadFile(const char *File) { if (!d->Vm || !d->Callback) { LAssert(0); return; } LFile f; if (f.Open(File, O_READ)) d->Script = f.Read(); else d->Script.Empty(); d->Obj.Reset(); if (d->Callback->CompileScript(d->Obj, File, d->Script)) { LCompiledCode *Code = dynamic_cast(d->Obj.Get()); if (Code) { d->Return.Empty(); d->Vm->d->Frames.Length(0); LScriptArguments Args(d->Vm, &d->Return); d->Vm->d->Setup(Code, 0, d->Log, NULL, &Args); } } } int LVmDebuggerWnd::OnCommand(int Cmd, int Event, OsView Wnd) { if (d->Vm && d->Vm->d->Vm == NULL) { // This happens when the original VM decides to go away and leave // our copy of the VM as the only one left. This means we have to // update the pointer in the VM's private data to point to us. d->Vm->d->Vm = d->Vm; } switch (Cmd) { case IDC_RUN: { if (d->Vm) d->Vm->Continue(); break; } case IDC_PAUSE: { if (d->Vm) d->Vm->BreakExecution(); break; } case IDC_STOP: { d->Vm.Reset(); if (d->RunLoop) d->RunLoop = false; else Quit(); break; } case IDC_RESTART: { if (d->Vm && d->Obj) { LCompiledCode *Code = dynamic_cast(d->Obj.Get()); if (Code) d->Vm->Execute(Code, 0, d->Log, false); } break; } case IDC_GOTO: { break; } case IDC_STEP_INSTR: { if (d->Vm) d->Vm->StepInstruction(); break; } case IDC_STEP_LINE: { if (d->Vm) d->Vm->StepLine(); break; } case IDC_STEP_OUT: { if (d->Vm) d->Vm->StepOut(); break; } case IDC_BREAK_POINT: { int Addr = d->Text->GetAddr(); if (Addr >= 0) d->Vm->BreakPoint(Addr, d->Text->Breakpoint(Addr)); break; } case IDC_BREAK_CPP: { if (!d->Vm) { LAssert(0); break; } LMenuItem *i = Menu->FindItem(IDC_BREAK_CPP); if (!i) { LAssert(0); break; } bool b = !i->Checked(); i->Checked(b); d->Vm->SetBreakCpp(b); break; } } return LWindow::OnCommand(Cmd, Event, Wnd); } int LVmDebuggerWnd::OnNotify(LViewI *Ctrl, LNotification n) { if (!d->AcceptNotify) return 0; switch (Ctrl->GetId()) { case IDC_TABS: { switch (Ctrl->Value()) { case 0: // Variables { if (d->Obj) { UpdateVariables(d->Globals, d->Vm->d->Scope[SCOPE_GLOBAL], d->Obj->Globals.Length(), 'G'); } if (d->Vm->d->Frames.Length()) { LVirtualMachinePriv::StackFrame &frm = d->Vm->d->Frames.Last(); UpdateVariables(d->Locals, d->Vm->d->Scope[SCOPE_LOCAL], frm.CurrentFrameSize, 'L'); } else d->Locals->Empty(); UpdateVariables(d->Registers, d->Vm->d->Scope[SCOPE_REGISTER], MAX_REGISTER, 'R'); break; } case 1: // Call stack { d->Stack->Empty(); LArray &Frames = d->Vm->d->Frames; for (int i=(int)Frames.Length()-1; i>=0; i--) { LVirtualMachinePriv::StackFrame &Sf = Frames[i]; LListItem *li = new LListItem; LString s; s.Printf("%p/%i", Sf.ReturnIp, Sf.ReturnIp); li->SetText(s, 0); const char *Src = d->Vm->d->Code->AddrToSourceRef(Sf.ReturnIp); li->SetText(Src, 1); d->Stack->Insert(li); } break; } case 2: // Log { break; } } break; } case IDC_SOURCE_LST: { if (n.Type == LNotifyItemSelect) { LListItem *it = d->SourceLst->GetSelected(); if (!it) break; const char *full = it->GetText(1); if (!LFileExists(full)) break; LoadFile(full); } break; } } return LWindow::OnNotify(Ctrl, n); } LMessage::Param LVmDebuggerWnd::OnEvent(LMessage *Msg) { return LWindow::OnEvent(Msg); } ///////////////////////////////////////////////////////////////////////////// bool LScriptArguments::Throw(const char *File, int Line, const char *Msg, ...) { if (!Vm || !Vm->d) return false; va_list Arg; va_start(Arg, Msg); LString s; s.Printf(Arg, Msg); va_end(Arg); Vm->d->OnException(File, Line, -1, s); return true; } diff --git a/src/common/Coding/Scripting.cpp b/src/common/Coding/Scripting.cpp --- a/src/common/Coding/Scripting.cpp +++ b/src/common/Coding/Scripting.cpp @@ -1,3445 +1,3445 @@ /// \file /// \author Matthew Allen /// #include #include #include #include "Lgi.h" #include "LToken.h" #include "Scripting.h" #include "GLexCpp.h" #include "GScriptingPriv.h" #include "GUtf8.h" -extern GHostFunc SystemLibrary[]; +extern LHostFunc SystemLibrary[]; #define ThisToken() (Cur < Tokens.Length() ? Tokens[Cur] : 0) #define NextToken() (Cur < Tokens.Length() - 1 ? Tokens[Cur + 1] : 0) // #define IsDigit(c) ( ((c)>='0') && ((c)<='9') ) enum GStatementType { GS_UNKNOWN, GS_IF, GS_FOR, GS_FUNCTION_DEFN, GS_FUNCTION_CALL, GS_LVALUE, }; /// Executable statement struct GCode { struct GConditionBlock { /// Index of the expression to evaluate int Condition; /// The code to execute if the expression is true LArray Block; }; typedef LArray GCondBlockArr; /// The type of statement GStatementType Type; /// The start token for the statement int Start; /// Child code element LArray Code; union { struct { /// Index of the statement executed at the start of the loop LArray *PreStatement; /// The index of the condition expression int Condition; /// Index of the statement to execute after the loop body is executed LArray *PostStatement; } For; struct { GCondBlockArr *Blocks; } If; struct { /// The command to call LFunc *Method; /// Indexes to the start of each argument expression LArray *Args; } MethodCall; struct { /// The operator after the L-value LOperator Op; /// The index of the expression int Expression; } LValue; }; ~GCode() { switch (Type) { default: break; case GS_IF: { DeleteObj(If.Blocks); break; } case GS_FOR: { DeleteObj(For.PreStatement); DeleteObj(For.PostStatement); break; } case GS_FUNCTION_CALL: { DeleteObj(MethodCall.Args); break; } case GS_LVALUE: { break; } } } }; class LExternType; class LExternType { public: LVariantType Simple; LArray Struct; int Sizeof() { switch (Simple) { default: break; case GV_INT32: return sizeof(int32); case GV_INT64: return sizeof(int64); case GV_STRING: return sizeof(char*); case GV_DOUBLE: return sizeof(double); case GV_NULL: { int s = 0; for (int i=0; iSizeof(); } return s; } } LAssert(0); return 0; } LExternType() { Simple = GV_NULL; } }; struct LExternFunc : public LFunc { LScriptEnginePrivate *Priv; char *Library; LExternType Return; LArray Args; LExternFunc(LScriptEnginePrivate *p) : LFunc(0, ExternFunc) { Priv = p; Library = 0; } ~LExternFunc() { DeleteArray(Library); Args.DeleteObjects(); } LExecutionStatus Call(LScriptContext *Ctx, LVariant *Ret, ArgumentArray &Args); }; struct LScriptFunc : public LFunc { LArray *BodyArr; int BodyIdx; LArray Args; LScriptEnginePrivate *Priv; LScriptFunc(LScriptEnginePrivate *p, char *meth = 0) : LFunc(meth, ScriptFunc) { Priv = p; BodyArr = 0; BodyIdx = -1; } ~LScriptFunc() { Args.DeleteArrays(); } LExecutionStatus Call(LScriptContext *Ctx, LVariant *Ret, ArgumentArray &Args); }; struct GDomPart { char16 *Name; int Array; GDomPart() { Name = 0; Array = 0; } }; struct GDomAddr { LArray Parts; char16 *operator[](int i) { if (i >= 0 && i < Parts.Length()) { return Parts[i].Name; } LAssert(0); return 0; } int Length() { return Parts.Length(); } void Add(char16 *part, int array = 0) { GDomPart *p = &Parts[Parts.Length()]; p->Name = part; p->Array = array; } void Print() { for (int i=0; i"); } } }; struct GDomRef { LVariant *Var; /// Any member name, i.e. if Var is a DOM reference char *Member; /// start of expression to evaluate for array index int Array; /// True if we own the 'Var' object bool FreeVar; GDomRef() { Var = 0; Member = 0; Array = 0; FreeVar = false; } GDomRef(GDomRef &dr) { if ((FreeVar = dr.FreeVar)) { if (dr.Var && (Var = new LVariant) != 0) { *Var = *dr.Var; } } else Var = dr.Var; Array = dr.Array; Member = NewStr(dr.Member); } ~GDomRef() { DeleteArray(Member); if (FreeVar) DeleteObj(Var); } bool Get(LScriptEnginePrivate *Priv, LVariant &v); bool Set(LScriptEnginePrivate *Priv, LVariant &v); }; bool InsertUtfCharacter(LVariant *Var, LVariant &v, int Idx) { bool Status = false; uint8 *Start = (uint8*) Var->CastString(); uint8 *Str = Start; if (Str) { int OldLen = strlen((char*) Str); int Len = OldLen; uint32 ch = *Str, i = 0; do { if (i++ == Idx) break; } while ((ch = LgiUtf8To32(Str, Len))); if (ch) { int NewC = 0, OldC = Len; uint8 *Temp = Str; int TempLen = Len; LgiUtf8To32(Temp, TempLen); OldC -= TempLen; char NewBytes[8]; uint8 *s = (uint8*) NewBytes; if (v.Type == GV_STRING) { uint8 *f = (uint8*) v.Str(); // from if (IsUtf8_1Byte(*f)) { *s++ = *f; } else if (IsUtf8_Lead(*f)) { *s++ = *f++; while (IsUtf8_Trail(*f)) *s++ = *f++; } NewC = s - (uint8*)NewBytes; } else { int l = sizeof(NewBytes); LgiUtf32To8(v.CastInt32(), s, l); NewC = sizeof(NewBytes) - l; } if (NewC > OldC) { // Re-alloc memory to make space for larger character char *Ins = new char[OldLen + (NewC - OldC) + 1]; if (Ins) { char *i = Ins; memcpy(i, Start, Str - Start); i += Str - Start; memcpy(i, NewBytes, NewC); i += NewC; memcpy(i, Str + OldC, Len - OldC + 1); Var->Empty(); Var->Type = GV_STRING; Var->Value.String = Ins; Status = true; } } else { // Just insert char in existing memory for (int n=0; n Stack; LScriptContext *Context; GHashTbl Methods; bool IsCompiling; LArray Compiled; LArray Tokens; LArray Lines; LScriptEnginePrivate(GScriptEngine1 *e) { Engine = e; Script = 0; IsCompiling = false; Stack.Add(new StackFrame); } ~LScriptEnginePrivate() { Empty(true); } char *ErrorTitle() { return (char*) (IsCompiling ? "CompileError" : "ExecuteError"); } LExecutionStatus Call(LFunc *Fn, LVariant *Ret, LArray *Idx) { if (!Fn) return ScriptError; ArgumentArray Args, Mem; ProcessArguments(Args, Mem, Idx); LExecutionStatus Status = Fn->Call(Context, Ret, Args); Mem.DeleteObjects(); return Status; } void Empty(bool Deleting = false) { DeleteArray(Script); Tokens.DeleteArrays(); Lines.Length(0); Compiled.Length(0); Stack.DeleteObjects(); if (!Deleting) Stack.Add(new StackFrame); LArray Del; for (void *v = Methods.First(); v; v = Methods.Next()) { LFunc *i = (LFunc*)v; if (i->Type != HostFunc) { Del.Add(i); } } for (int i=0; iMethod); DeleteObj(Del[i]); } } void DumpCode(LArray &c, int Depth = 0) { char Tabs[265]; memset(Tabs, '\t', Depth); Tabs[Depth] = 0; for (int i=0; iCondition : 0, Tabs); break; } case GS_FOR: { LgiTrace("%sfor (%p; %i; %p)\n%s{\n", Tabs, Code.For.PreStatement, Code.For.Condition, Code.For.PostStatement, Tabs); break; } case GS_FUNCTION_CALL: { LgiTrace("%sfunc:%s(", Tabs, Code.MethodCall.Method->Method); if (Code.MethodCall.Args) { int Len = Code.MethodCall.Args->Length(); for (int n=0; n &Compiled, char *script) { bool Status = false; // Lex script into tokens int Cur = Tokens.Length(); if (LexScript(script)) { // Do compile stage IsCompiling = true; while (Cur < Tokens.Length()) { // int Start = Cur; if (!Compile_Statement(Compiled, Cur)) { if (Compiled.Length() > 0) Compiled.Length(Compiled.Length()-1); Status = false; break; } else Status = true; } IsCompiling = false; } return Status; } void ProcessArguments(ArgumentArray &Args, ArgumentArray &Mem, LArray *Idx); int FindEndOfBlock(int Cur); LVariant *Var(char16 *name, bool create = true); bool ToVar(LVariant &v, char16 *s); bool Require(int &Cur, const char *tok); bool Type(int &Cur, class LExternType &Type); bool CallExtern(int &Cur, uint32 &ReturnValue, struct LExternFunc *Func); // Dom addressing bool CreateDomAddr(int &Cur, GDomAddr &Addr); bool ResolveDomAddr(struct GDomRef &Ref, GDomAddr &Addr, bool Create); // Compiling of code bool Compile_MethodCall(int &Cur); bool Compile_Expression(int &Cur, int Depth = 0); bool Compile_Statement(LArray &To, int &Cur); // Execution of code LVariant *Execute_Expression(int &Cur, GDom *Src = 0, int Depth = 0); LExecutionStatus Execute_Statement(LArray &To); }; bool GDomRef::Get(LScriptEnginePrivate *Priv, LVariant &v) { if (Var) { if (Member) { if (Var->Type == GV_DOM) { if (Var->Value.Dom) { LVariant *ArrVal = 0; if (Array) { int Cur = Array; ArrVal = Priv->Execute_Expression(Cur); } bool Ret = Var->Value.Dom->GetVariant(Member, v, ArrVal ? ArrVal->CastString() : 0); DeleteObj(ArrVal); if (!Ret) { Priv->Term.Print("%s - Couldn't resolve member '%s'\n", Priv->ErrorTitle(), Member); } return Ret; } } else if (Var->Type == GV_STRING) { if (stricmp(Member, "length") == 0) { v = (int) strlen(Var->Str()); return true; } else { Priv->Term.Print("%s - Couldn't resolve member '%s'\n", Priv->ErrorTitle(), Member); } } else if (Var->Type == GV_DATETIME) { if (Var->Value.Date) { if (stricmp(Member, "day") == 0) { v = Var->Value.Date->Day(); } else if (stricmp(Member, "month") == 0) { v = Var->Value.Date->Month(); } else if (stricmp(Member, "year") == 0) { v = Var->Value.Date->Year(); } else if (stricmp(Member, "hour") == 0) { v = Var->Value.Date->Hours(); } else if (stricmp(Member, "minute") == 0) { v = Var->Value.Date->Minutes(); } else if (stricmp(Member, "second") == 0) { v = Var->Value.Date->Seconds(); } else return false; return true; } else { Priv->Term.Print("%s - Couldn't resolve member '%s'\n", Priv->ErrorTitle(), Member); } } else if (Var->Type == GV_LIST) { if (stricmp(Member, "length") == 0) { v = Var->Value.Lst->Length(); return true; } else { Priv->Term.Print("%s - Couldn't resolve member '%s'\n", Priv->ErrorTitle(), Member); } } else if (Var->Type == GV_HASHTABLE) { if (stricmp(Member, "length") == 0) { v = Var->Value.Hash->Length(); return true; } else { Priv->Term.Print("%s - Couldn't resolve member '%s'\n", Priv->ErrorTitle(), Member); } } } else { bool Status = false; if (Array) { int Cur = Array; LVariant *a = Priv->Execute_Expression(Cur); if (a) { if (Var->Type == GV_STRING) { int Idx = a->CastInt32(); uint8 *Str = (uint8*) Var->CastString(); if (Str) { int Len = strlen((char*) Str); int i = 0; uint32 ch; while ((ch = LgiUtf8To32(Str, Len))) { if (i++ == Idx) { v = (int)ch; Status = true; break; } } } } else if (Var->Type == GV_HASHTABLE) { char *Key = a->CastString(); LVariant *h = (LVariant*) Var->Value.Hash->Find(Key); if (h) { v = *h; } else { v.Empty(); } Status = true; } else if (Var->Type == GV_LIST) { int Idx = a->CastInt32(); LVariant *h = (*Var->Value.Lst)[Idx]; if (h) { v = *h; Status = true; } } else { Priv->Term.Print("%s:%i - Can't index into type '%i'.\n", __FILE__, __LINE__, Var->Type); } DeleteObj(a); } else Priv->Term.Print("%s:%i - Can't evaluate array expression.\n", __FILE__, __LINE__); } else { v = *Var; Status = true; } return Status; } } return false; } bool GDomRef::Set(LScriptEnginePrivate *Priv, LVariant &v) { if (Var) { if (Member) { if (Var->Type == GV_DOM) { if (Var->Value.Dom) { return Var->Value.Dom->SetValue(Member, v); } } else if (Var->Type == GV_STRING || Var->Type == GV_NULL) { if (stricmp(Member, "length") == 0) { Var->Empty(); int Len = v.CastInt32(); Var->Type = GV_STRING; if ((Var->Value.String = new char[Len+1])) { memset(Var->Value.String, ' ', Len); Var->Value.String[Len] = 0; return true; } } } else if (Var->Type == GV_DATETIME) { if (Var->Value.Date) { if (stricmp(Member, "day") == 0) { Var->Value.Date->Day(v.CastInt32()); } else if (stricmp(Member, "month") == 0) { Var->Value.Date->Month(v.CastInt32()); } else if (stricmp(Member, "year") == 0) { Var->Value.Date->Year(v.CastInt32()); } else if (stricmp(Member, "hour") == 0) { Var->Value.Date->Hours(v.CastInt32()); } else if (stricmp(Member, "minute") == 0) { Var->Value.Date->Minutes(v.CastInt32()); } else if (stricmp(Member, "second") == 0) { Var->Value.Date->Seconds(v.CastInt32()); } else return false; return true; } } } else { if (Array) { bool Status = false; int Cur = Array; LVariant *a = Priv->Execute_Expression(Cur); if (a) { if (Var->Type == GV_STRING) { Status = InsertUtfCharacter(Var, v, a->CastInt32()); } else if (Var->Type == GV_HASHTABLE) { char *Key = a->CastString(); LVariant *n = (LVariant*) Var->Value.Hash->Find(Key); if (!n) { Var->Value.Hash->Add(Key, n = new LVariant); } if (n) { *n = v; Status = true; } } else if (Var->Type == GV_LIST) { int Idx = a->CastInt32(); if (Idx < 0 || Idx >= Var->Value.Lst->Length()) { // Add a new element LVariant *h = new LVariant(v); if (h) { Var->Value.Lst->Insert(h); Status = true; } } else { // Change an existing element LVariant *h = (*Var->Value.Lst)[Idx]; if (h) *h = v; Status = true; } } else { Priv->Term.Print("%s:%i - Can't index into type '%i'.\n", __FILE__, __LINE__, Var->Type); LAssert(0); } DeleteObj(a); } else Priv->Term.Print("%s:%i - Can't evaluate array expression.\n", __FILE__, __LINE__); } else { *Var = v; return true; } } } return false; } #define CompToInt(v) \ (v[0] == '0' && v[1] == 'x') ? htoi(v + 2) : atoi(v); GScriptEngine1::GScriptEngine1(LViewI *parent, LScriptContext *context) { d = new LScriptEnginePrivate(this); d->Parent = parent; d->Context = context; d->Script = 0; int i = 0; - for (GHostFunc *cmd = SystemLibrary + i; cmd->Method; cmd++) + for (LHostFunc *cmd = SystemLibrary + i; cmd->Method; cmd++) { d->Methods.Add(cmd->Method, cmd); } if (d->Context) { d->Context->SetEngine(this); - GHostFunc *CmdTable = d->Context->GetCommands(); + LHostFunc *CmdTable = d->Context->GetCommands(); if (CmdTable) { - for (GHostFunc *cmd = CmdTable; cmd->Method; cmd++) + for (LHostFunc *cmd = CmdTable; cmd->Method; cmd++) { d->Methods.Add(cmd->Method, (LFunc*)cmd); } } } } GScriptEngine1::~GScriptEngine1() { if (d->Context) { d->Context->SetEngine(0); } d->Tokens.DeleteArrays(); DeleteObj(d); } LVariant *GScriptEngine1::Var(char16 *name, bool create) { return d->Var(name, create); } LStringPipe *GScriptEngine1::GetTerm() { return &d->Term; } bool GScriptEngine1::CallMethod(const char *Method, LVariant *Ret, ArgumentArray &Args) { bool Status = false; if (Method) { LFunc *Func = (LFunc*) d->Methods.Find(Method); if (Func) { Status = Func->Call(d->Context, Ret, Args); } } return Status; } bool LScriptEnginePrivate::CreateDomAddr(int &Cur, GDomAddr &Addr) { char16 *t = ThisToken(); if (t && isalpha(*t)) { Addr.Add(t); Cur++; // Build DOM address while ((t = ThisToken())) { if (StricmpW(t, sPeriod) == 0) { Cur++; t = ThisToken(); if (isalpha(*t)) { Cur++; Addr.Add(t); } else { Term.Print("%s:%i - Expecting name after '.'.\n", ErrorTitle(), Lines[Cur]); break; } } else if (StricmpW(t, sStartSqBracket) == 0) { Cur++; int Start = Cur; Addr.Parts[Addr.Length()-1].Array = Start; if (Compile_Expression(Cur)) { t = ThisToken(); if (t && StricmpW(t, sEndSqBracket) == 0) { Cur++; } else { Term.Print("%s:%i - Expecting ']'.\n", ErrorTitle(), Lines[Cur]); break; } } else { Term.Print("%s:%i - Expression error.\n", ErrorTitle(), Lines[Start]); break; } } else break; } return true; } return false; } bool LScriptEnginePrivate::ResolveDomAddr(GDomRef &Ref, GDomAddr &Addr, bool Create) { if (Addr.Length() == 1) { if ((Ref.Var = Var(Addr[0], Create))) { Ref.Array = Addr.Parts[0].Array; } return Ref.Var != 0; } else if (Addr.Length() > 1) { Ref.Var = Var(Addr[0], Create); if (Ref.Var) { for (int i=1; iType == GV_DOM && Ref.Var->Value.Dom) { LVariant *v = new LVariant; if (v) { char *c = LgiNewUtf16To8(Addr[i]); if (c) { char *ArrayVal = 0; int ArrayPos = Addr.Parts[i].Array; if (ArrayPos) { LVariant *a = Execute_Expression(ArrayPos); if (a) { ArrayVal = NewStr(a->Str()); DeleteObj(a); } } bool Has = Ref.Var->Value.Dom->GetVariant(c, *v, ArrayVal); DeleteArray(c); if (Has) { if (Ref.FreeVar) DeleteObj(Ref.Var); Ref.Var = v; Ref.FreeVar = true; v = 0; } else { DeleteObj(v); return false; } } DeleteObj(v); } else return false; } else return false; } GDomPart &Part = Addr.Parts[Addr.Length()-1]; Ref.Member = LgiNewUtf16To8(Part.Name); Ref.Array = Part.Array; return true; } else { Term.Print("%s - Failed to resolve '%S'\n", ErrorTitle(), Addr[0]); } } return false; } void GScriptEngine1::Empty() { d->Empty(); } bool GScriptEngine1::Compile(char *Script, const char *FileName, bool Add) { bool Status = false; if (!Add) d->Empty(); if (Script) { Status = d->CompileTo(d->Compiled, Script); #if 0 d->DumpCode(d->Compiled); #endif } return Status; } LExecutionStatus GScriptEngine1::Run() { if (d->Compiled.Length() > 0) { // Run it return d->Execute_Statement(d->Compiled); } return ScriptError; } class ScriptTokenState { LScriptEnginePrivate *d; int TokLen; int LinesLen; public: ScriptTokenState(LScriptEnginePrivate *priv) { d = priv; TokLen = d->Tokens.Length(); LinesLen = d->Lines.Length(); } ~ScriptTokenState() { for (int i=TokLen; iTokens.Length(); i++) { DeleteArray(d->Tokens[i]); } d->Tokens.Length(TokLen); d->Lines.Length(LinesLen); } }; LExecutionStatus GScriptEngine1::RunTemporary(char *Script) { LExecutionStatus Status = ScriptError; if (Script) { ScriptTokenState State(d); LArray Compiled; if (d->CompileTo(Compiled, Script)) { if (Compiled.Length() > 0) { Status = d->Execute_Statement(Compiled); } } } return Status; } bool GScriptEngine1::EvaluateExpression(LVariant *Result, GDom *VariableSource, char *Expression) { bool Status = false; if (Result && Expression) { ScriptTokenState State(d); int Cur = d->Tokens.Length(); if (d->LexScript(Expression)) { LVariant *v = d->Execute_Expression(Cur, VariableSource); if (v) { *Result = *v; DeleteObj(v); Status = true; } } } return Status; } void GScriptEngine1::DumpVariables() { d->Term.Print("Variables:\n"); char *VarName; for (int i=0; iStack.Length(); i++) { StackFrame *sf = d->Stack[i]; for (LVariant *v = (LVariant*)sf->First(&VarName); v; v = (LVariant*)sf->Next(&VarName)) { char t[128]; ZeroObj(t); memset(t, ' ', (i + 1) * 4); switch (v->Type) { case GV_NULL: d->Term.Print("%s%s = null\n", t, VarName); break; case GV_BOOL: d->Term.Print("%s%s = (bool) %s\n", t, VarName, (char*) (v->Value.Bool ? "true" : "false")); break; case GV_INT32: d->Term.Print("%s%s = (int) %i\n", t, VarName, v->CastInt32()); break; case GV_DOUBLE: d->Term.Print("%s%s = (double) %f\n", t, VarName, v->CastDouble()); break; case GV_STRING: d->Term.Print("%s%s = (string) \"%s\"\n", t, VarName, v->Str()); break; case GV_DOM: d->Term.Print("%s%s = (GDom*) 0x%p\n", t, VarName, v->Value.Dom); break; case GV_VOID_PTR: d->Term.Print("%s%s = (void*) 0x%p\n", t, VarName, v->Value.Ptr); break; default: d->Term.Print("%s%s = (type %i) ?????\n", t, VarName, v->Type); break; } } } } LVariant *LScriptEnginePrivate::Var(char16 *name, bool create) { if (name) { char b[256]; int Len = StrlenW(name) * sizeof(*name); int Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)name, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; LVariant *v = 0; for (int i=Stack.Length()-1; i>=0; i--) { StackFrame *sf = Stack[i]; if ((v = (LVariant*) sf->Find(b))) { break; } } if (!v && create) { if ((v = new LVariant)) { Stack[Stack.Length()-1]->Add(b, v); } } return v; } return 0; } bool LScriptEnginePrivate::ToVar(LVariant &v, char16 *s) { if (s) { char16 *f = *s == '\"' ? s + 1 : s; bool IsNumber = false; if (f[0] == '0' && tolower(f[1]) == 'x') { v = htoi(f + 2); return true; } else if (IsDigit(*f) || *f == '-' || *f == '.') { IsNumber = true; for (char16 *c = f; *s; s++) { if (*c == '\"' || *c == ' ' || *c == '\t' || *c == '\r' || *c == '\n') break; else if (!IsDigit(*c) && !strchr("-e.", *c)) { IsNumber = false; break; } } } if (IsNumber) { if (StrchrW(f, '.')) { v = atof(f); return true; } else { v = atoi(f); return true; } } else { v.Empty(); v.Type = GV_STRING; if (*s == '\"') { int Len = StrlenW(s); v.Value.String = LgiNewUtf16To8(s + 1, (Len - 2) * sizeof(*s)); } else { v.Value.String = LgiNewUtf16To8(s); } return true; } } return false; } bool LScriptEnginePrivate::Require(int &Cur, const char *tok) { if (tok && Cur >= 0 && Cur < Tokens.Length()) { char16 *t = Tokens[Cur]; if (t) { char b[64]; int Len = StrlenW(t) * sizeof(*t); if (Len < 32) { int Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)t, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; if (stricmp(b, tok) == 0) { Cur++; return true; } } } } return false; } bool LScriptEnginePrivate::Compile_MethodCall(int &Cur) { char16 *t = ThisToken(); if (t) { if (!Require(Cur, "(")) { Term.Print("%s:%i - Expecting '(' after method name\n", ErrorTitle(), Lines[Cur]); return false; } while ((t = ThisToken())) { if (*t == ')') { break; } else if (*t == ',') { Cur++; } else { if (!Compile_Expression(Cur)) { break; } } } if (!Require(Cur, ")")) { Term.Print("%s:%i - Expecting '(' after method name\n", ErrorTitle(), Lines[Cur]); return false; } else return true; } return false; } bool LScriptEnginePrivate::Compile_Expression(int &Cur, int Depth) { if (Cur >= 0 && Cur < Tokens.Length()) { int Args = 0; int Ops = 0; int StartToken = Cur; int PrevIsOp = -1; char16 *t; while ((t = Tokens[Cur])) { if (StricmpW(t, sStartRdBracket) == 0) { Cur++; if (Compile_Expression(Cur, Depth + 1)) { Args++; PrevIsOp = 0; } else { break; } } else if (StricmpW(t, sEndRdBracket) == 0) { if (Depth > 0) Cur++; break; } else if (StricmpW(t, sComma) == 0 || StricmpW(t, sSemiColon) == 0) { break; } else if (Depth == 0 && StricmpW(t, sEndSqBracket) == 0) { break; } else { LOperator Top = IsOp(t, PrevIsOp); if (Top != OpNull) { if (PrevIsOp == 1) { LBase u; u.NameW(t); Term.Print("%s:%i - Not expecting '%s', 2nd operator in a row.\n", ErrorTitle(), Lines[StartToken], u.Name()); break; } else { Ops++; PrevIsOp = 1; } } else { if (PrevIsOp == 0) { LBase u; u.NameW(t); Term.Print("%s:%i - Not expecting '%s' after non-operator token.\n", ErrorTitle(), Lines[StartToken], u.Name()); break; } char m[64], *mp = m; for (char16 *c = t; *c && mp < m + sizeof(m) - 1; c++) { *mp++ = *c; } *mp++ = 0; if (Methods.Find(m)) { Cur++; if (!Compile_MethodCall(Cur)) { return false; } Args++; Cur--; } else { GDomAddr Addr; if (CreateDomAddr(Cur, Addr)) { Cur--; Args++; } else { Args++; } } PrevIsOp = 0; } Cur++; } } if (Args > 1 && (Ops == Args - 1 || Ops == Args)) { return true; } else if (Args == 1) { return true; } } else { Term.Print("%s:%i - Unexpected end of file.\n", __FILE__, __LINE__); } return false; } LVariant *LScriptEnginePrivate::Execute_Expression(int &Cur, GDom *Src, int Depth) { // Internally to this function we use the LVariant::User member to // store non-zero if we don't own the LVariant object. And thus // shouldn't free the memory on exit. if (Cur >= 0 && Cur < Tokens.Length()) { LArray Args; int StartToken = Cur; int PrevIsOp = -1; char16 *t; while ((t = Tokens[Cur])) { if (StricmpW(t, sStartRdBracket) == 0) { Cur++; LVariant *r = Execute_Expression(Cur, Src, Depth + 1); if (r) { LAssert(r->Type != GV_NULL); Args.Add(r); PrevIsOp = 0; } else { Term.Print("%s:%i - Expression returned NULL (Line %i)\n", __FILE__, __LINE__, Lines[StartToken]); break; } } else if (StricmpW(t, sEndRdBracket) == 0) { if (Depth > 0) Cur++; break; } else if (StricmpW(t, sComma) == 0 || StricmpW(t, sSemiColon) == 0) { break; } else if (Depth == 0 && StricmpW(t, sEndSqBracket) == 0) { break; } else { LOperator Top = IsOp(t, PrevIsOp); if (Top != OpNull) { if (PrevIsOp == 1) { LBase u; u.NameW(t); Term.Print("%s:%i - '%s' was the 2nd operator in a row.\n", ErrorTitle(), Lines[StartToken], u.Name()); break; } else { Args.Add(new LVariant(Top)); PrevIsOp = 1; } } else { if (PrevIsOp == 0) { if (*t == '-') { // Emit a minus operator and treat the value as a number t++; Args.Add(new LVariant(OpMinus)); } else { LBase u; u.NameW(t); Term.Print("%s:%i - Not expecting '%s' after non-operator.\n", ErrorTitle(), Lines[StartToken], u.Name()); break; } } LFunc *Method = 0; char16 *Next = NextToken(); if (Next && Next[0] == '(' && Next[1] == 0) { char Name[256]; const void *In = t; int InLen = StrlenW(t) * sizeof(*t); int Converted = LBufConvertCp(Name, "utf-8", sizeof(Name)-1, In, LGI_WideCharset, InLen); Name[Converted] = 0; Method = (LFunc*) Methods.Find(Name); if (Method) { LArray Idx; ArgumentArray Arg, Mem; LVariant *Ret = new LVariant; // Collect arguments Cur += 2; while ((t = ThisToken())) { if (*t == ')') { Cur++; break; } else if (*t == ',') { Cur++; } else { Idx.Add(Cur); Compile_Expression(Cur); } } // Call method ProcessArguments(Arg, Mem, &Idx); if (Method->Call(Context, Ret, Arg)) { LAssert(Ret->Type != GV_NULL); Args.Add(Ret); PrevIsOp = 0; } else DeleteObj(Ret); Mem.DeleteObjects(); Cur--; } else { Term.Print("%s:%i - Method '%s' not defined. (Line %i)\n", __FILE__, __LINE__, Name, Lines[StartToken]); } } if (!Method) { LVariant *v = 0; GDomRef *Ref = 0; int Start = Cur; GDomAddr Addr; if (CreateDomAddr(Cur, Addr)) { if (Src) { // Using variable source // Set up the variable name as a NULL terminated wide string LArray a; for (int i=Start; iGetValue(utf, *v); // Clean up... DeleteArray(utf); Cur--; } else { Cur--; // Normal DOM reference, resolve it here Ref = new GDomRef; if (!ResolveDomAddr(*Ref, Addr, false)) { // Variable name... LAssert(Ref->Array == 0); DeleteObj(Ref); if ((v = new LVariant)) ToVar(*v, t); } } } else { if ((v = new LVariant)) ToVar(*v, t); } if (v) { if (v->Type == GV_NULL) { return NULL; } Args.Add(v); } else if (Ref && Ref->Var) { if (Ref->Var->Type == GV_DOMREF) { GDom *dom = Ref->Var->CastDom(); if (dom) Args.Add(new LVariant(dom, Ref->Member)); else LAssert(!"No DOM ptr."); } else { if (Ref->Array) { switch (Ref->Var->Type) { case GV_LIST: { char16 *a = Tokens[Ref->Array]; int idx = atoi(a); LVariant *k = Ref->Var->Value.Lst->ItemAt(idx); if (k) { k->User = true; Args.Add(k); } else { Term.Print("%s:%i - List idx %i doesn't exist.\n", ErrorTitle(), Lines[StartToken], idx); Args.Add(new LVariant); } break; } case GV_HASHTABLE: { char16 *a = Tokens[Ref->Array]; char *u = 0; LVariant *k = 0; if (a && (u = LgiNewUtf16To8(a)) != 0 && (k = (LVariant*) Ref->Var->Value.Hash->Find(u)) != 0) { k->User = true; Args.Add(k); } else { Term.Print("%s:%i - Hash element '%s' doesn't exist.\n", ErrorTitle(), Lines[StartToken], u); Args.Add(new LVariant); } DeleteArray(u); break; } case GV_DOM: { if (Ref->Var->Value.Dom) { int Exp = Ref->Array; LVariant *Idx = Execute_Expression(Exp, 0, 0); if (Idx) { LVariant *Result = new LVariant; if (Ref->Var->Value.Dom->GetVariant(Ref->Member, *Result, Idx->CastString())) { Args.Add(Result); } else DeleteObj(Result); DeleteObj(Idx); } } break; } default: { Term.Print("%s:%i - Array lookup not support for variant type %i.\n", ErrorTitle(), Lines[StartToken], Ref->Var->Type); Args.Add(new LVariant); break; } } } else { Ref->Var->User = !Ref->FreeVar; if (Ref->Member) { LVariant *t = new LVariant; if (t) { Ref->Get(this, *t); Args.Add(t); } } else { Args.Add(Ref->Var); Ref->Var = 0; } } } } PrevIsOp = 0; } } Cur++; } } if (Args.Length() > 1) { // Loop through and combine values and operators according to precedence while (Args.Length() > 1) { // Find which operator to handle first int LowestOp = -1; int LowestPre = -1; for (int i=0; iType); if (Args[i]->Type == GV_OPERATOR) { int p = GetPrecedence(Args[i]->Value.Op); if (LowestOp < 0 || p < LowestPre) { LowestPre = p; LowestOp = i; } } } LAssert(LowestOp >= 0 && LowestOp < Args.Length()); OperatorType Type = OpType(Args[LowestOp]->Value.Op); if (Type == OpPrefix) { if (LowestOp >= Args.Length() - 1) { Term.Print("%s:%i - Prefix operator followed by nothing.\n", ErrorTitle(), Lines[StartToken]); break; } else { LVariant *Op = Args[LowestOp]; LVariant *Var = Args[LowestOp + 1]; LVariant r; switch (Op->Value.Op) { case OpUnaryPlus: { // do nothing break; } case OpUnaryMinus: { // Negate the argument if (Var->Type == GV_DOUBLE) { r = -Var->CastDouble(); } else if (r.Type == GV_INT64) { r = -Var->CastInt64(); } else { r = -Var->CastInt32(); } break; } case OpPreInc: { // Add one if (Var->Type == GV_DOUBLE) { r = Var->CastDouble() + 1; } else if (Var->Type == GV_INT64) { r = Var->CastInt64() + 1; } else { r = Var->CastInt32() + 1; } break; } case OpPreDec: { // Minus one if (Var->Type == GV_DOUBLE) { r = Var->CastDouble() - 1; } else if (Var->Type == GV_INT64) { r = Var->CastInt64() - 1; } else { r = Var->CastInt32() - 1; } break; } case OpNot: { // Logical not if (Var->Type == GV_DOUBLE) { r = !Var->CastDouble(); } else if (Var->Type == GV_INT64) { r = !Var->CastInt64(); } else { r = !Var->CastInt32(); } break; } default: { LAssert(!"Not a valid prefix operator"); break; } } if (Var->Type == GV_DOMREF) { Var->Value.Dom->SetValue(Var->Value.DomRef.Name, r); } else if (Var->User) { Args[LowestOp+1] = new LVariant(r); } else { *Var = r; } // Remove the prefix op for the array Args.DeleteAt(LowestOp, true); } } else if (Type == OpPostfix) { if (LowestOp == 0) { Term.Print("%s:%i - Postfix operator preceded by nothing.\n", ErrorTitle(), Lines[StartToken]); break; } else { LVariant *Op = Args[LowestOp]; LVariant *Var = Args[LowestOp - 1]; LVariant r; switch (Op->Value.Op) { case OpPostInc: { // Add one if (Var->Type == GV_DOUBLE) { r = Var->CastDouble() + 1; } else if (Var->Type == GV_INT64) { r = Var->CastInt64() + 1; } else { r = Var->CastInt32() + 1; } break; } case OpPostDec: { // Minus one if (Var->Type == GV_DOUBLE) { r = Var->CastDouble() - 1; } else if (Var->Type == GV_INT64) { r = Var->CastInt64() - 1; } else { r = Var->CastInt32() - 1; } break; } default: { LAssert(!"Not a valid postfix operator"); Term.Print("%s:%i - Not a valid postfix operator.\n", ErrorTitle(), Lines[StartToken]); break; } } if (Var->Type == GV_DOMREF) { Var->Value.Dom->SetValue(Var->Value.DomRef.Name, r); } else if (Var->User) { Args[LowestOp-1] = new LVariant(r); } else { *Var = r; } // Remove the postfix op for the array Args.DeleteAt(LowestOp, true); } } else if (Type == OpInfix) { if (LowestOp >= (Args.Length() - 1) || LowestOp <= 0) { Term.Print("%s:%i - Infix operator missing argument.\n", ErrorTitle(), Lines[StartToken]); Args.Length(0); break; } LVariant *a = Args[LowestOp - 1]; LVariant *op = Args[LowestOp]; LVariant *b = Args[LowestOp + 1]; LVariant r; LAssert(op->Value.Op); switch (op->Value.Op) { case OpPlus: { if (a->Type == GV_DOUBLE || b->Type == GV_DOUBLE) { r = a->CastDouble() + b->CastDouble(); } else if (a->Type == GV_STRING || b->Type == GV_STRING) { LStringPipe p; #define ToStr(v) \ if (v->Type == GV_DOM) p.Print("%p", v->Value.Dom); \ else if (v->Type == GV_VOID_PTR) p.Print("%p", v->Value.Ptr); \ else p.Print("%s", v->CastString()); ToStr(a); ToStr(b); r.Type = GV_STRING; r.Value.String = p.NewStr(); } else { r = a->CastInt32() + b->CastInt32(); } break; } case OpMinus: { if (a->Type == GV_DOUBLE || b->Type == GV_DOUBLE) { r = a->CastDouble() - b->CastDouble(); } else { r = a->CastInt32() - b->CastInt32(); } break; } case OpMul: { if (a->Type == GV_DOUBLE || b->Type == GV_DOUBLE) { r = a->CastDouble() * b->CastDouble(); } else { r = a->CastInt32() * b->CastInt32(); } break; } case OpMod: { if (a->Type == GV_DOUBLE || b->Type == GV_DOUBLE) { r = fmod(a->CastDouble(), b->CastDouble()); } else { r = a->CastInt32() % b->CastInt32(); } break; } case OpDiv: { if (a->Type == GV_DOUBLE || b->Type == GV_DOUBLE) { double Denom = b->CastDouble(); if (Denom != 0.0) { r = a->CastDouble() / Denom; } } else { int Denom = b->CastInt32(); if (Denom) { r = a->CastInt32() / Denom; } } break; } case OpLessThan: { if (a->Type == GV_DOUBLE || b->Type == GV_DOUBLE) { r = a->CastDouble() < b->CastDouble(); } else { r = a->CastInt32() < b->CastInt32(); } break; } case OpGreaterThan: { if (a->Type == GV_DOUBLE || b->Type == GV_DOUBLE) { r = a->CastDouble() > b->CastDouble(); } else { r = a->CastInt32() > b->CastInt32(); } break; } case OpLessThanEqual: { if (a->Type == GV_DOUBLE || b->Type == GV_DOUBLE) { r = a->CastDouble() <= b->CastDouble(); } else { r = a->CastInt32() <= b->CastInt32(); } break; } case OpGreaterThanEqual: { if (a->Type == GV_DOUBLE || b->Type == GV_DOUBLE) { r = a->CastDouble() >= b->CastDouble(); } else { r = a->CastInt32() >= b->CastInt32(); } break; } case OpEquals: { if (a->Type == GV_DOUBLE || b->Type == GV_DOUBLE) { r = a->CastDouble() == b->CastDouble(); } else { r = a->CastInt32() == b->CastInt32(); } break; } case OpNotEquals: { if (a->Type == GV_DOUBLE || b->Type == GV_DOUBLE) { r = a->CastDouble() != b->CastDouble(); } else { r = a->CastInt32() != b->CastInt32(); } break; } case OpAnd: { r = a->CastInt32() && b->CastInt32(); break; } case OpOr: { r = a->CastInt32() || b->CastInt32(); break; } default: { Term.Print("%s:%i - Unhandled operator '%i'\n", ErrorTitle(), Lines[StartToken], op->Value.Op); break; } } if (a->Type == GV_DOMREF) { a->Value.Dom->SetValue(a->Value.DomRef.Name, r); } else if (a->User) { Args[LowestOp-1] = new LVariant(r); } else { *a = r; } Args.DeleteAt(LowestOp+1, true); Args.DeleteAt(LowestOp, true); } else LAssert(!"Unknown operator type"); } } if (Args.Length() == 1) { LVariant *v = Args[0]; return v->User ? new LVariant(*v) : v; } } else { Term.Print("%s:%i - Unexpected end of file.\n", __FILE__, __LINE__); } return 0; } int LScriptEnginePrivate::FindEndOfBlock(int Cur) { int Depth = 1; while (Cur < Tokens.Length()) { char16 *t = Tokens[Cur]; if (StricmpW(t, sEndSqBracket) == 0) { Depth++; } else if (StricmpW(t, sEndSqBracket) == 0) { Depth--; if (Depth == 0) { return Cur; break; } } Cur++; } return -1; } bool LScriptEnginePrivate::Type(int &Cur, LExternType &Type) { char16 *t = Tokens[Cur]; if (t) { if (StricmpW(t, sInt) == 0 || StricmpW(t, sUInt) == 0 || StricmpW(t, sUInt) == 0 || StricmpW(t, sUInt32) == 0 || StrcmpW(t, sHWND) == 0 || StrcmpW(t, sDWORD) == 0) { Type.Simple = GV_INT32; } else if (StricmpW(t, sInt64) == 0) { Type.Simple = GV_INT64; } else if (StricmpW(t, sLPCTSTR) == 0 || StricmpW(t, sLPTSTR) == 0) { Type.Simple = GV_STRING; } else if (StricmpW(t, sChar) == 0) { t = Tokens[Cur + 1]; if (t && t[0] == '*' && !t[1]) { Cur++; Type.Simple = GV_STRING; } } else { return false; } Cur++; return true; } return false; } LExecutionStatus LExternFunc::Call(LScriptContext *Ctx, LVariant *Ret, ArgumentArray &In) { if (!Priv || !Library || !Method) return ScriptError; LExecutionStatus Status = ScriptError; LArray ArgVal; LArray Mem; for (int i=0; iTokens[Cur]; char16 *t2 = Priv->Tokens[Cur + 1]; LVariant *v = t1 && isalpha(*t1) && t2 && (*t2 == ',' || *t2 == ')') ? Priv->Var(t1, false) : 0; */ LVariant *v = In[i]; if (v) { switch (Args[i]->Simple) { case GV_INT32: { ArgVal[i] = v->CastInt32(); break; } case GV_INT64: { ArgVal[i] = v->CastInt32(); break; } case GV_STRING: { if (v->Str()) { ArgVal[i] = (NativeInt) v->Str(); } else { ArgVal[i] = (NativeInt) v->CastString(); } break; } default: { LAssert(0); break; } } } } if (ArgVal.Length() == Args.Length()) { LLibrary Lib(Library); if (Lib.IsLoaded()) { void *c = Lib.GetAddress(Method); if (c) { #if defined(_MSC_VER) uint32 a = ArgVal.Length(); NativeInt b = (NativeInt) &ArgVal[Args.Length()-1]; #endif uint32 r = 0; #if defined(_MSC_VER) && !defined(_WIN64) _asm { mov ecx, a mov ebx, b } label1: _asm { push [ebx] sub ebx, 4 loop label1 mov ebx, c call ebx mov r, eax } #else // Not implemented LAssert(0); #endif if (Ret) (*Ret) = (int) r; Status = ScriptSuccess; } else if (Priv) { Priv->Term.Print("%s - Extern library '%s' missing function '%s'.\n", Priv->ErrorTitle(), Library, Method); } } else if (Priv) { Priv->Term.Print("%s - Extern library '%s' missing.\n", Priv->ErrorTitle(), Library); } } else if (Priv) { Priv->Term.Print("%s - Extern '%s' expecting %i arguments, not %i given.\n", Priv->ErrorTitle(), Method, Args.Length(), ArgVal.Length()); } Mem.DeleteArrays(); return Status; } LExecutionStatus LScriptFunc::Call(LScriptContext *Ctx, LVariant *Ret, ArgumentArray &Values) { if (!BodyArr || BodyIdx < 0 || !Priv) return ScriptError; LExecutionStatus Status = ScriptError; // Process the arguments into values... if (Args.Length() == Values.Length()) { // Set up new stack frame... LVariant *n; StackFrame *sf = new StackFrame; if (!sf) return ScriptError; // Add to the stack Priv->Stack.Add(sf); // Set copies of all the arguments as local variables for (int i=0; iAdd(Args[i], (void*)n); } } // Call the method LAssert(BodyIdx < BodyArr->Length()); GCode *Body = &(*BodyArr)[BodyIdx]; LAssert(Body->Type == GS_FUNCTION_DEFN); Status = Priv->Execute_Statement(Body->Code); // Remove the local stack frame Priv->Stack.Delete(sf); DeleteObj(sf); } else { Priv->Term.Print("%s - Script function '%s' was expecting %i arguments, not the %i given.\n", Priv->ErrorTitle(), Method, Args.Length(), Values.Length()); } return Status; } void LScriptEnginePrivate::ProcessArguments(ArgumentArray &Args, ArgumentArray &Mem, LArray *Idx) { if (Idx) { for (int i=0; iLength(); i++) { int Cur = (*Idx)[i]; char16 *Tok = ThisToken(); char16 *Next = NextToken(); LVariant *v = 0; if (Tok && Next && isalpha(*Tok) && (*Next == ')' || *Next == ',')) { v = Var(Tok, false); } if (v) { Args[i] = v; } else { LVariant *v = Execute_Expression(Cur); if (!v) { v = new LVariant; } if (v) { Args[i] = v; Mem.Add(v); } } } } } LExecutionStatus LScriptEnginePrivate::Execute_Statement(LArray &To) { LExecutionStatus Status = ScriptError; for (int k=0; kLength(); Blk++) { GCode::GConditionBlock &b = (*Code.If.Blocks)[Blk]; int Cur = b.Condition; if (!Cur) { Block = &b; break; } LVariant *e = Execute_Expression(Cur); if (e) { bool IsTrue; switch (e->Type) { case GV_VOID_PTR: IsTrue = e->Value.Ptr != 0; break; case GV_DOM: IsTrue = e->Value.Dom != 0; break; case GV_STRING: IsTrue = e->Value.String != 0; break; default: IsTrue = e->CastInt32(); break; } DeleteObj(e); if (IsTrue) { Block = &b; break; } } else { Term.Print("%s:%i - Couldn't evaluate if statement's condition.\n", ErrorTitle(), Lines[Code.Start]); } } if (Block) { Status = Execute_Statement(Block->Block); } else Status = ScriptSuccess; break; } case GS_FUNCTION_DEFN: { // Do nothing here... break; } case GS_FUNCTION_CALL: { if (!Code.MethodCall.Method) break; LVariant Ret; Status = Call(Code.MethodCall.Method, &Ret, Code.MethodCall.Args); if (!Status) { Term.Print("%s:%i - Command '%s' failed.\n", ErrorTitle(), Lines[Code.Start], Code.MethodCall.Method->Method); } break; } case GS_FOR: { if (Code.For.PreStatement) { if (!Execute_Statement(*Code.For.PreStatement)) { Term.Print("%s:%i - Failed to execute for loop's PreStatement.\n", ErrorTitle(), Lines[Code.Start]); break; } } while (true) { // Evaluate the condition int Cur = Code.For.Condition; LVariant *v = Execute_Expression(Cur); if (!v) { break; } bool Cond = v->CastInt32() != 0; DeleteObj(v); if (!Cond) { Status = ScriptSuccess; break; } // Execute the body Execute_Statement(Code.Code); // Execute the post statement if (Code.For.PostStatement) { if (!Execute_Statement(*Code.For.PostStatement)) { Term.Print("%s:%i - Failed to execute for loop's PostStatement.\n", ErrorTitle(), Lines[Code.Start]); break; } } } break; } case GS_LVALUE: { GDomAddr Dom; int Cur = Code.Start; if (CreateDomAddr(Cur, Dom)) { switch (Code.LValue.Op) { case OpAssign: { Cur = Code.LValue.Expression; LVariant *v = Execute_Expression(Cur); if (v) { GDomRef Ref; /* printf("Assigning to "); Dom.Print(); printf("\n"); */ if (ResolveDomAddr(Ref, Dom, true)) { if (Ref.Member) { if (Ref.Var->Type == GV_DOM && Ref.Var->Value.Dom) { Ref.Var->Value.Dom->SetValue(Ref.Member, *v); } else if (Ref.Var->Type == GV_STRING || Ref.Var->Type == GV_NULL) { if (stricmp(Ref.Member, "length") == 0) { Ref.Var->Empty(); int Len = v->CastInt32(); char *Mem = new char[Len+1]; if (Mem) { memset(Mem, ' ', Len); Mem[Len] = 0; Ref.Var->Value.String = Mem; Ref.Var->Type = GV_STRING; } } } } else { if (Ref.Array) { Ref.Set(this, *v); } else { // Pure assignment.. *Ref.Var = *v; } } Status = ScriptSuccess; } DeleteObj(v); } else { Term.Print("%s:%i - Failed to handle expression after assignment.\n", ErrorTitle(), Lines[Code.Start]); } break; } case OpPostInc: { LVariant *v = Var(Dom[0]); if (v) { *v = v->CastInt32() + 1; Status = ScriptSuccess; } break; } case OpPostDec: { LVariant *v = Var(Dom[0]); if (v) { *v = v->CastInt32() - 1; Status = ScriptSuccess; } break; } case OpPlusEquals: { Cur = Code.LValue.Expression; LVariant *v = Execute_Expression(Cur); if (v) { GDomRef Ref; if (ResolveDomAddr(Ref, Dom, true)) { if (Ref.Member) { if (Ref.Var->Type == GV_DOM && Ref.Var->Value.Dom) { LVariant a; if (Ref.Var->Value.Dom->GetValue(Ref.Member, a)) { LVariant b = a.CastInt32() + v->CastInt32(); Ref.Var->Value.Dom->SetValue(Ref.Member, b); } } else if (Ref.Var->Type == GV_STRING || Ref.Var->Type == GV_NULL) { LAssert(0); /* if (stricmp(Ref.Member, "length") == 0) { Ref.Var->Empty(); int Len = v->CastInt32(); char *Mem = new char[Len+1]; if (Mem) { memset(Mem, ' ', Len); Mem[Len] = 0; Ref.Var->Value.String = Mem; Ref.Var->Type = GV_STRING; } } */ } } else { *Ref.Var = Ref.Var->CastInt32() + v->CastInt32(); } Status = ScriptSuccess; } DeleteObj(v); } else { Term.Print("%s:%i - Failed to handle expression after assignment.\n", ErrorTitle(), Lines[Code.Start]); } break; } default: { LAssert(0); break; } } } break; } default: { LAssert(0); return ScriptError; break; } } } return ScriptSuccess; } bool LScriptEnginePrivate::Compile_Statement(LArray &To, int &Cur) { bool Status = false; int StartToken = Cur; char16 *t = Cur < Tokens.Length() ? Tokens[Cur++] : 0; if (t) { if (StricmpW(t, sIf) == 0) { GCode &Code = To[To.Length()]; Code.Type = GS_IF; Code.Start = Cur - 1; Code.If.Blocks = new GCode::GCondBlockArr; ParseNextIfStatement: if (!Require(Cur, "(")) { Term.Print("%s:%i - Missing '(' after 'if'.\n", ErrorTitle(), Lines[StartToken]); } else { GCode::GConditionBlock &b = Code.If.Blocks->New(); b.Condition = Cur; if (!Compile_Expression(Cur)) { Term.Print("%s:%i - Couldn't compile if statement's condition expression.\n", ErrorTitle(), Lines[StartToken]); } else { if (!Require(Cur, ")")) { Term.Print("%s:%i - Missing ')' after if statement's expression.\n", ErrorTitle(), Lines[Cur]); } else { if (!Require(Cur, "{")) { Term.Print("%s:%i - Missing start block '{' in if statement.\n", ErrorTitle(), Lines[Cur]); } else { while ((t = ThisToken())) { if (StricmpW(t, sEndCurlyBracket) == 0) { Status = true; Cur++; break; } else { if (!Compile_Statement(b.Block, Cur)) { break; } } } if (Status && (t = ThisToken()) != 0 && StricmpW(t, sElse) == 0) { Cur++; // There are only 2 things that can follow an 'else' keyword, // either the start of a block '{' or another 'if' keyword. if (!(t = ThisToken())) { Term.Print("%s:%i - Missing token after 'else'.\n", ErrorTitle(), Lines[Cur-1]); } else if (!StricmpW(t, sIf)) { // It's an 'else if' statement Cur++; goto ParseNextIfStatement; } else if (!StricmpW(t, sStartCurlyBracket)) { // It's an 'else' block if (!Require(Cur, "{")) { Term.Print("%s:%i - Missing start block '{' after else statement.\n", ErrorTitle(), Lines[Cur]); } else { GCode::GConditionBlock &b = Code.If.Blocks->New(); b.Condition = 0; while ((t = ThisToken())) { if (StricmpW(t, sEndCurlyBracket) == 0) { Status = true; Cur++; break; } else { if (!Compile_Statement(b.Block, Cur)) { break; } } } } } else { Term.Print("%s:%i - Error after 'else'.\n", ErrorTitle(), Lines[Cur-1]); } } } } } } } else if (StricmpW(t, sFunction) == 0) { GCode &Code = To[To.Length()]; Code.Type = GS_FUNCTION_DEFN; Code.Start = Cur - 1; if ((t = ThisToken())) { LScriptFunc *Func = 0; if (!isalpha(*t)) { Term.Print("%s:%i - Function name must start with a letter.\n", ErrorTitle(), Lines[Cur]); goto EndFuncDefn; } Func = new LScriptFunc(this); if (Func) { Func->Method = LgiNewUtf16To8(t); Func->BodyArr = &To; Func->BodyIdx = To.Length() - 1; Methods.Add(Func->Method, (LFunc*)Func); if (!Require(++Cur, "(")) { Term.Print("%s:%i - Missing '(' in funcion defn argument list.\n", ErrorTitle(), Lines[Cur]); goto EndFuncDefn; } // Read any arguments while ((t = ThisToken())) { if (isalpha(*t)) { Func->Args.Add(LgiNewUtf16To8(t)); } else if (StricmpW(t, sComma) == 0) { // Skip } else if (StricmpW(t, sEndRdBracket) == 0) { break; } else { char *t8 = LgiNewUtf16To8(t); Term.Print("%s:%i - Unexpected token '%s' in argument defn.\n", ErrorTitle(), Lines[Cur], t8); DeleteArray(t8); } Cur++; } if (!Require(Cur, ")")) { Term.Print("%s:%i - Missing ')' in funcion defn argument list.\n", ErrorTitle(), Lines[Cur]); goto EndFuncDefn; } if (!Require(Cur, "{")) { Term.Print("%s:%i - Expecting '{' after function name.\n", ErrorTitle(), Lines[Cur]); goto EndFuncDefn; } Status = true; do { t = ThisToken(); if (*t != '}') { if (!Compile_Statement(Code.Code, Cur)) { Status = false; break; } } } while (StricmpW(t, sEndCurlyBracket) != 0); if (Require(Cur, "}")) { Func = 0; } else { Status = false; Term.Print("%s:%i - Expecting '}' after function body.\n", ErrorTitle(), Lines[Cur]); } } EndFuncDefn: if (Func) { Methods.Delete(Func->Method); DeleteObj(Func); } } else { Term.Print("%s:%i - Expecting function name.\n", ErrorTitle(), Lines[Cur-1]); } } else if (StricmpW(t, sExtern) == 0) { LExternFunc *Func = new LExternFunc(this); if (Func) { // Parse the return type if (Type(Cur, Func->Return)) { char16 *t = Tokens[Cur]; if (t && isalpha(*t)) { // Store the method name Func->Method = LgiNewUtf16To8(t); Cur++; if (!Require(Cur, "(")) { Term.Print("%s:%i - Expecting '('.\n", ErrorTitle(), Lines[Cur]); } else { // Parse arguments to the func while (true) { LExternType *k = new LExternType; if (k) { if (Type(Cur, *k)) { Func->Args.Add(k); } else { Term.Print("%s:%i - Expecting type.\n", ErrorTitle(), Lines[Cur]); break; } t = Tokens[Cur]; if (t && *t == ',') { Cur++; } else break; } else break; } if (!Require(Cur, ")")) { Term.Print("%s:%i - Expecting ')'.\n", ErrorTitle(), Lines[Cur]); } else if (!Require(Cur, "in")) { Term.Print("%s:%i - Expecting 'in'.\n", ErrorTitle(), Lines[Cur]); } else { t = Tokens[Cur]; if (t) { Cur++; LVariant v; ToVar(v, t); Func->Library = NewStr(v.Str()); if (!Require(Cur, ";")) { Term.Print("%s:%i - Expecting ';'.\n", ErrorTitle(), Lines[Cur]); } else { Methods.Add(Func->Method, (LFunc*)Func); Status = true; Func = 0; } } } } } else { Term.Print("%s:%i - Expecting method name.\n", ErrorTitle(), Lines[Cur]); } } DeleteObj(Func); } } else if (StricmpW(t, sFor) == 0) { GCode &Code = To[To.Length()]; Code.Type = GS_FOR; Code.Start = Cur - 1; // For loop if (!Require(Cur, "(")) { Term.Print("%s:%i - Missing '(' after 'for'.\n", ErrorTitle(), Lines[StartToken]); } else { // Execute the initial statement if ((Code.For.PreStatement = new LArray) != NULL && Compile_Statement(*Code.For.PreStatement, Cur)) { // Store the start position of the condition tokens Code.For.Condition = Cur; // Compile the condition Compile_Expression(Cur); if (Require(Cur, ";")) { // Skip over the post statement if ((Code.For.PostStatement = new LArray) != NULL && Compile_Statement(*Code.For.PostStatement, Cur)) { // See the start of the block... if (Require(Cur, "{")) { // Execute content while ((t = Tokens[Cur])) { if (StricmpW(t, sEndCurlyBracket) == 0) { Status = true; Cur++; break; } else if (!Compile_Statement(Code.Code, Cur)) { break; } } } else { Term.Print("%s:%i - Missing '{'.\n", ErrorTitle(), Lines[Cur]); } } else { Term.Print("%s:%i - Failed to compile post statement.\n", ErrorTitle(), Lines[StartToken]); } } else { Term.Print("%s:%i - Missing ';' after expression.\n", ErrorTitle(), Lines[StartToken]); } } } } else { // See if this token is a method char *t8 = LgiNewUtf16To8(t); LFunc *Func = (LFunc*) Methods.Find(t8); if (Func) { GCode &Code = To[To.Length()]; Code.Type = GS_FUNCTION_CALL; Code.Start = Cur - 1; Code.MethodCall.Args = 0; Code.MethodCall.Method = Func; if (!Require(Cur, "(")) { Term.Print("%s:%i - Missing beginning '(' function call.\n", ErrorTitle(), Lines[Cur]); } else { // Parse through any arguments. while (true) { int Start = Cur; if (Compile_Expression(Cur)) { if (!Code.MethodCall.Args) { Code.MethodCall.Args = new LArray; } if (Code.MethodCall.Args) { Code.MethodCall.Args->Add(Start); } } t = Tokens[Cur]; if (t) { if (*t == ',') { Cur++; } else if (*t == ')') { Cur++; break; } else { LBase u; u.NameW(t); Term.Print("%s:%i - Not expecting token '%s' in argument list.\n", ErrorTitle(), Lines[Start], u.Name()); break; } } else break; } if (Require(Cur, ";")) { Status = true; } else { Term.Print("%s:%i - Missing ';' after function call.\n", ErrorTitle(), Lines[StartToken]); } } } else { // Not an external method, is it an L-value? GDomAddr Dom; Cur--; if (CreateDomAddr(Cur, Dom)) { // Look at operator char16 *n = ThisToken(); if (n) { LOperator Op = IsOp(n, 0); switch (Op) { case OpPlusEquals: case OpAssign: case OpMinusEquals: case OpMulEquals: case OpDivEquals: { GCode &Code = To[To.Length()]; Code.Type = GS_LVALUE; Code.Start = StartToken; Code.LValue.Op = Op; Code.LValue.Expression = ++Cur; if (Compile_Expression(Cur)) { t = ThisToken(); if (t) { if (StricmpW(t, sSemiColon) == 0) { Cur++; Status = true; } else if (StricmpW(t, sEndRdBracket) == 0) { Cur++; Status = true; } } } else { Term.Print("%s:%i - Failed to compile expression after assignment.\n", ErrorTitle(), Lines[StartToken]); } break; } case OpPostInc: case OpPostDec: { GCode &Code = To[To.Length()]; Code.Type = GS_LVALUE; Code.Start = StartToken; Code.LValue.Op = Op; Code.LValue.Expression = ++Cur; t = ThisToken(); if (t) { if (StricmpW(t, sSemiColon) == 0) { Cur++; Status = true; } else if (StricmpW(t, sEndRdBracket) == 0) { Cur++; Status = true; } } break; } default: { Term.Print("%s:%i - '%S' isn't an operator.\n", ErrorTitle(), Lines[StartToken], n); break; } } } } } DeleteArray(t8); } } else { Term.Print("%s:%i - Unexpected end of file in statement.\n", __FILE__, __LINE__); } return Status; } diff --git a/src/common/Coding/ScriptingPriv.h b/src/common/Coding/ScriptingPriv.h --- a/src/common/Coding/ScriptingPriv.h +++ b/src/common/Coding/ScriptingPriv.h @@ -1,562 +1,562 @@ #ifndef _GSCRIPTING_PRIV_H_ #define _GSCRIPTING_PRIV_H_ #include #include "lgi/common/Scripting.h" #include "lgi/common/RefCount.h" // Instructions #define _i(name, opcode, desc) \ name = opcode, #define AllInstructions \ _i(INop, 0, "Nop") \ _i(IAssign, OpAssign, "OpAssign") \ _i(IPlus, OpPlus, "OpPlus") \ _i(IUnaryPlus, OpUnaryPlus, "OpUnaryPlus") \ _i(IMinus, OpMinus, "OpMinus") \ _i(IUnaryMinus, OpUnaryMinus, "OpUnaryMinus") \ _i(IMul, OpMul, "OpMul") \ _i(IDiv, OpDiv, "OpDiv") \ _i(IMod, OpMod, "OpMod") \ _i(ILessThan, OpLessThan, "OpLessThan") \ _i(ILessThanEqual, OpLessThanEqual, "OpLessThanEqual") \ _i(IGreaterThan, OpGreaterThan, "OpGreaterThan") \ _i(IGreaterThanEqual, OpGreaterThanEqual, "OpGreaterThanEqual") \ _i(IEquals, OpEquals, "OpEquals") \ _i(INotEquals, OpNotEquals, "OpNotEquals") \ _i(IPlusEquals, OpPlusEquals, "OpPlusEquals") \ _i(IMinusEquals, OpMinusEquals, "OpMinusEquals") \ _i(IMulEquals, OpMulEquals, "OpMulEquals") \ _i(IDivEquals, OpDivEquals, "OpDivEquals") \ _i(IPostInc, OpPostInc, "OpPostInc") \ _i(IPostDec, OpPostDec, "OpPostDec") \ _i(IPreInc, OpPreInc, "OpPreInc") \ _i(IPreDec, OpPreDec, "OpPreDec") \ _i(IAnd, OpAnd, "OpAnd") \ _i(IOr, OpOr, "OpOr") \ _i(INot, OpNot, "OpNot") \ \ /** Calls a another part of the script */ \ _i(ICallScript, 64, "CallScript") \ /** Calls a method defined by the script context */ \ _i(ICallMethod, 65, "CallMethod") \ _i(IDomGet, 67, "DomGet") \ _i(IDomSet, 68, "DomSet") \ _i(IPush, 69, "Push") \ _i(IPop, 70, "Pop") \ _i(IJump, 71, "Jump") \ _i(IJumpZero, 72, "JumpZ") \ _i(IArrayGet, 73, "ArrayGet") \ _i(IArraySet, 74, "ArraySet") \ _i(IRet, 75, "Return") \ _i(IDomCall, 76, "DomCall") \ /* Stop in the VM at instruction */ \ _i(IBreakPoint, 77, "BreakPoint") \ _i(ICast, 78, "Cast") \ /* Open the debugger */ \ _i(IDebug, 79, "Debug") \ enum GInstruction { AllInstructions }; enum OperatorType { OpPrefix, OpInfix, OpPostfix, }; extern char16 sChar[]; extern char16 sInt[]; extern char16 sUInt[]; extern char16 sInt32[]; extern char16 sUInt32[]; extern char16 sInt64[]; extern char16 sHWND[]; extern char16 sDWORD[]; extern char16 sLPTSTR[]; extern char16 sLPCTSTR[]; extern char16 sElse[]; extern char16 sIf[]; extern char16 sFunction[]; extern char16 sExtern[]; extern char16 sFor[]; extern char16 sWhile[]; extern char16 sReturn[]; extern char16 sInclude[]; extern char16 sDefine[]; extern char16 sStruct[]; extern char16 sTrue[]; extern char16 sFalse[]; extern char16 sNull[]; extern char16 sOutParam[]; extern char16 sHash[]; extern char16 sPeriod[]; extern char16 sComma[]; extern char16 sSemiColon[]; extern char16 sStartRdBracket[]; extern char16 sEndRdBracket[]; extern char16 sStartSqBracket[]; extern char16 sEndSqBracket[]; extern char16 sStartCurlyBracket[]; extern char16 sEndCurlyBracket[]; extern const char *InstToString(GInstruction i); /* Variable Reference: Can either be a: - stack variable - global variable - register (0 .. MAX_REGISTER - 1) Thus a variable reference encodes 2 pieces of information, first the scope of the reference (as above) and secondly the index into that scope. Scopes are stored as arrays of variables. The scope is one byte, the index is 3 bytes following, totally 4 bytes per ref. */ #define MAX_REGISTER 16 /// 32bit variable reference, used to track where a variable is during compilation. struct LVarRef { /// \sa #SCOPE_REGISTER, #SCOPE_LOCAL or #SCOPE_GLOBAL unsigned Scope : 8; /// Index into scope int Index : 24; bool Valid() { return Index >= 0; } void Empty() { Scope = 0; Index = -1; } bool IsReg() { return Scope == SCOPE_REGISTER && Index >= 0 && Index < MAX_REGISTER; } void SetReg(int i) { Scope = SCOPE_REGISTER; Index = i; } bool operator ==(LVarRef &r) { return r.Scope == Scope && r.Index == Index; } bool operator !=(LVarRef &r) { return r.Scope != Scope || r.Index != Index; } const char *GetStr() { if (Index < 0) { LAssert(!"Invalid reference"); return "NoRef"; } #define GETSTR_BUF_SIZE 16 static char Buf[8][GETSTR_BUF_SIZE]; static int Cur = 0; static char Names[] = {'R', 'L', 'G'}; char *b = Buf[Cur++]; if (Cur >= 8) Cur = 0; LAssert(Scope <= SCOPE_GLOBAL); sprintf_s(b, GETSTR_BUF_SIZE, "%c%i", Names[Scope], Index); return b; } }; -union GPtr +union LScriptPtr { uint8_t *u8; uint16 *u16; uint32_t *u32; int8 *i8; int16 *i16; int32 *i32; double *dbl; float *flt; LVarRef *r; LFunc **fn; }; class SystemFunctions; class LCompileTools { protected: OperatorType OpType(LOperator o) { switch (o) { case OpUnaryPlus: case OpUnaryMinus: case OpPreInc: case OpPreDec: case OpNot: return OpPrefix; case OpPostInc: case OpPostDec: return OpPostfix; default: return OpInfix; } } int GetPrecedence(LOperator o) { // Taken from: // http://www.cppreference.com/operator_precedence.html switch (o) { case OpAssign: case OpMinusEquals: case OpPlusEquals: case OpMulEquals: case OpDivEquals: return 16; case OpAnd: return 13; case OpOr: return 14; case OpEquals: case OpNotEquals: return 9; case OpLessThan: case OpLessThanEqual: case OpGreaterThan: case OpGreaterThanEqual: return 8; case OpPlus: case OpMinus: return 6; case OpMul: case OpDiv: case OpMod: return 5; case OpUnaryPlus: case OpUnaryMinus: case OpPreInc: case OpPreDec: case OpNot: return 3; case OpPostInc: case OpPostDec: return 2; case OpNull: return 0; default: LAssert(!"Really?"); break; } return -1; } LOperator IsOp(char16 *s, int PrevIsOp) { if (!s) return OpNull; if (s[0] != 0 && !s[1]) { // One character operator switch (*s) { case '=': return OpAssign; case '*': return OpMul; case '/': return OpDiv; case '<': return OpLessThan; case '>': return OpGreaterThan; case '%': return OpMod; case '!': return OpNot; case '+': { if (PrevIsOp == 0) return OpPlus; return OpUnaryPlus; } case '-': { if (PrevIsOp == 0) return OpMinus; return OpUnaryMinus; } } } else if (s[0] != 0 && s[1] == '=' && !s[2]) { // 2 chars, "something" equals operator switch (*s) { case '!': return OpNotEquals; case '=': return OpEquals; case '<': return OpLessThanEqual; case '>': return OpGreaterThanEqual; case '+': return OpPlusEquals; case '-': return OpMinusEquals; case '*': return OpMulEquals; case '/': return OpDivEquals; } } else if (s[0] == '+' && s[1] == '+' && !s[2]) { if (PrevIsOp == 0) return OpPostInc; return OpPreInc; } else if (s[0] == '-' && s[1] == '-' && !s[2]) { if (PrevIsOp == 0) return OpPostDec; return OpPreDec; } else if (s[0] == '&' && s[1] == '&' && !s[2]) { return OpAnd; } else if (s[0] == '|' && s[1] == '|' && !s[2]) { return OpOr; } return OpNull; } }; /// This class compiles the source down to byte code class LCompiler : public LScriptUtils { class LCompilerPriv *d; public: /// Constructor LCompiler(); ~LCompiler(); /// Compile the source into byte code. bool Compile ( LAutoPtr &Code, LScriptContext *SysContext, LScriptContext *UserContext, const char *FileName, const char *Script, LDom *Args ); }; /// This class is the VM for the byte language class LVirtualMachine : public LScriptUtils { friend class LVmDebuggerWnd; friend class LScriptArguments; class LVirtualMachinePriv *d; public: static bool BreakOnWarning; - LVirtualMachine(LVmDebuggerCallback *callback = NULL); + LVirtualMachine(LVmCallback *callback = NULL); LVirtualMachine(LVirtualMachine *vm); ~LVirtualMachine(); /// Executes the whole script starting at the top LExecutionStatus Execute ( /// [In] The code to execute LCompiledCode *Code, /// [In] The instruction to start at... [defaults to the start of script) uint32_t StartOffset = 0, /// [Optional] Log file for execution LStream *Log = NULL, /// Start the script execution straight away? bool StartImmediately = true, /// Optional return value LVariant *Return = NULL ); /// Execute just one method and return LExecutionStatus ExecuteFunction ( /// [In] The code to execute LCompiledCode *Code, /// [In] The function to execute LFunctionInfo *Func, /// [In/Out] The function's arguments LScriptArguments &Args, /// [Optional] Log file for execution LStream *Log = NULL, /// [Optional] Copy arguments back to this array LScriptArguments *ArgsOut = NULL ); // Debugging commands LVmDebugger *OpenDebugger(LCompiledCode *Code = NULL, const char *Assembly = NULL); bool StepInstruction(); bool StepLine(); bool StepOut(); bool BreakExecution(); bool Continue(); bool BreakPoint(const char *File, int Line, bool Add); bool BreakPoint(int Addr, bool Add); void SetBreakCpp(bool Brk); void SetDebuggerEnabled(bool b); // Properties void SetTempPath(const char *Path); + LVmCallback *GetCallback(); }; /// Scripting engine system functions class SystemFunctions : public LScriptContext { LScriptEngine *Engine; LStream *Log; #ifdef WINNATIVE HANDLE Brk; #endif LView *CastLView(LVariant &v); - bool WaitForReturn(LScriptArguments &Args); public: SystemFunctions(); ~SystemFunctions(); LStream *GetLog(); bool SetLog(LStream *log); void SetEngine(LScriptEngine *Eng); - char *GetIncludeFile(char *FileName) + LString GetIncludeFile(const char *FileName) override { return NULL; } - GHostFunc *GetCommands(); + LHostFunc *GetCommands(); // Debug bool Assert(LScriptArguments &Args); bool Throw(LScriptArguments &Args); bool DebuggerEnabled(LScriptArguments &Args); // String bool LoadString(LScriptArguments &Args); /// Formats a string bool Sprintf(LScriptArguments &Args); /// Formats a file size bool FormatSize(LScriptArguments &Args); /// Prints items to the console bool Print(LScriptArguments &Args); /// Converts args to string bool ToString(LScriptArguments &Args); // Object creation/deletion bool New(LScriptArguments &Args); bool Delete(LScriptArguments &Args); bool Len(LScriptArguments &Args); // File /// Reads a text file into a variable bool ReadTextFile(LScriptArguments &Args); /// Writes a text file from a variable bool WriteTextFile(LScriptArguments &Args); /// \brief Opens a file open dialog to select files. /// /// Args: LView *Parent, char *Patterns, /// char *InitFolder, bool Multiselect bool SelectFiles(LScriptArguments &Args); /// Open a folder select dialog /// /// Args: LView *Parent, char *InitFolder bool SelectFolder(LScriptArguments &Args); /// Lists file in folder /// /// Args; char *Path, [optional] char *Pattern /// Returns: List of DOM objects with the following fields: /// Name - File/dir name /// Size - Size of entry /// Folder - bool, true if folder /// Modified - LDateTime, modified time bool ListFiles(LScriptArguments &Args); /// Deletes a file bool DeleteFile(LScriptArguments &Args); /// Gets the current script path. bool CurrentScript(LScriptArguments &Args); /// Finds out if a path exists. bool PathExists(LScriptArguments &Args); /// Joins path segments together. bool PathJoin(LScriptArguments &Args); /// Returns the current OS path separator. bool PathSep(LScriptArguments &Args); // Time /// Sleeps a number of milliseconds bool Sleep(LScriptArguments &Args); /// Get the current tick count bool ClockTick(LScriptArguments &Args); /// Get the date time bool Now(LScriptArguments &Args); // Bitmaps /// Creates a memory context bool CreateSurface(LScriptArguments &Args); bool ColourSpaceToString(LScriptArguments &Args); bool StringToColourSpace(LScriptArguments &Args); // User interface /// Standard alert message box bool MessageDlg(LScriptArguments &Args); /// Gets an input string from the user - /// String GetInputDlg(Window Parent, String InitialValue, String Question, String Title[, bool IsPassword]); + /// String GetInputDlg(Window Parent, String InitialValue, String Question, String Title, bool IsPassword, String Callback. bool GetInputDlg(LScriptArguments &Args); /// Gets a view by id bool GetViewById(LScriptArguments &Args); // System /// Executes a command, waits for it to finish, then returns it's output: /// String Execute(String Application, String CmdLine); bool Execute(LScriptArguments &Args); /// Executes a command and doesn't wait for it to return: /// Bool System(String Application, String CmdLine); bool System(LScriptArguments &Args); /// Gets the operating system name. bool OsName(LScriptArguments &Args); /// Gets the operating system version. bool OsVersion(LScriptArguments &Args); }; #endif diff --git a/src/common/Lgi/Variant.cpp b/src/common/Lgi/Variant.cpp --- a/src/common/Lgi/Variant.cpp +++ b/src/common/Lgi/Variant.cpp @@ -1,2345 +1,2345 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" const char *LVariant::TypeToString(LVariantType t) { switch (t) { case GV_NULL: return "NULL"; case GV_INT32: return "int32"; case GV_INT64: return "int64"; case GV_BOOL: return "bool"; case GV_DOUBLE: return "double"; case GV_STRING: return "String"; case GV_BINARY: return "Binary"; case GV_LIST: return "List"; case GV_DOM: return "Dom"; case GV_DOMREF: return "DomReference"; case GV_VOID_PTR: return "VoidPtr"; case GV_DATETIME: return "DateTime"; case GV_HASHTABLE: return "HashTable"; case GV_OPERATOR: return "Operator"; case GV_CUSTOM: return "Custom"; case GV_WSTRING: return "WString"; case GV_GVIEW: return "View"; case GV_STREAM: return "Stream"; case GV_LSURFACE: return "Surface"; case GV_LMOUSE: return "MouseEvent"; case GV_LKEY: return "KeyboardEvent"; default: return "Unknown"; } return NULL; } const char *LVariant::OperatorToString(LOperator op) { switch (op) { case OpNull: return "OpNull"; case OpAssign: return "OpAssign"; case OpPlus: return "OpPlus"; case OpUnaryPlus: return "OpUnaryPlus"; case OpMinus: return "OpMinus"; case OpUnaryMinus: return "OpUnaryMinus"; case OpMul: return "OpMul"; case OpDiv: return "OpDiv"; case OpMod: return "OpMod"; case OpLessThan: return "OpLessThan"; case OpLessThanEqual: return "OpLessThanEqual"; case OpGreaterThan: return "OpGreaterThan"; case OpGreaterThanEqual: return "OpGreaterThanEqual"; case OpEquals: return "OpEquals"; case OpNotEquals: return "OpNotEquals"; case OpPlusEquals: return "OpPlusEquals"; case OpMinusEquals: return "OpMinusEquals"; case OpMulEquals: return "OpMulEquals"; case OpDivEquals: return "OpDivEquals"; case OpPostInc: return "OpPostInc"; case OpPostDec: return "OpPostDec"; case OpPreInc: return "OpPreInc"; case OpPreDec: return "OpPreDec"; case OpAnd: return "OpAnd"; case OpOr: return "OpOr"; case OpNot: return "OpNot"; } return NULL; } LVariant::LVariant() { Type = GV_NULL; ZeroObj(Value); } LVariant::LVariant(LVariant const &v) { Type = GV_NULL; ZeroObj(Value); *this = v; } #if LVARIANT_SIZET LVariant::LVariant(size_t i) { Type = GV_NULL; *this = i; } #endif #if LVARIANT_SSIZET LVariant::LVariant(ssize_t i) { Type = GV_NULL; *this = i; } #endif LVariant::LVariant(int32_t i) { Type = GV_INT32; Value.Int = i; } LVariant::LVariant(uint32_t i) { Type = GV_INT32; Value.Int = i; } LVariant::LVariant(int64_t i) { Type = GV_INT64; Value.Int64 = i; } LVariant::LVariant(uint64_t i) { Type = GV_INT64; Value.Int64 = i; } LVariant::LVariant(double i) { Type = GV_DOUBLE; Value.Dbl = i; } LVariant::LVariant(const char *s) { Value.String = NewStr(s); Type = Value.String ? GV_STRING : GV_NULL; } LVariant::LVariant(const char16 *s) { Value.WString = NewStrW(s); Type = Value.WString ? GV_WSTRING : GV_NULL; } LVariant::LVariant(void *p) { Type = GV_NULL; *this = p; } LVariant::LVariant(LDom *p) { Type = GV_NULL; *this = p; } LVariant::LVariant(LDom *p, char *name) { Type = GV_NULL; SetDomRef(p, name); } LVariant::LVariant(const LDateTime *d) { Type = GV_NULL; *this = d; } LVariant::LVariant(LOperator Op) { Type = GV_OPERATOR; Value.Op = Op; } LVariant::~LVariant() { Empty(); } bool LVariant::operator ==(LVariant &v) { switch (Type) { default: case GV_NULL: return v.Type == Type; case GV_INT32: return Value.Int == v.CastInt32(); case GV_INT64: return Value.Int64 == v.CastInt64(); case GV_BOOL: return Value.Bool == v.CastBool(); case GV_DOUBLE: return Value.Dbl == v.CastDouble(); case GV_STRING: { char *s = v.Str(); if (Value.String && s) return !strcmp(Value.String, s); break; } case GV_WSTRING: { char16 *w = v.WStr(); if (Value.WString && w) return !StrcmpW(Value.WString, w); break; } case GV_BINARY: { if (v.Type == Type && Value.Binary.Data == v.Value.Binary.Data && Value.Binary.Length == v.Value.Binary.Length) { return true; } break; } case GV_LIST: { if (!Value.Lst || !v.Value.Lst) return false; if (Value.Lst->Length() != v.Value.Lst->Length()) return false; auto ValIt = Value.Lst->begin(); auto VIt = v.Value.Lst->begin(); LVariant *a, *b; while ( (a = *ValIt) && (b = *VIt) ) { if (!(*a == *b)) return false; ValIt++; VIt++; } return true; } case GV_DOMREF: { return Value.DomRef.Dom == v.Value.DomRef.Dom && Value.DomRef.Name != 0 && v.Value.DomRef.Name != 0 && !stricmp(Value.DomRef.Name, v.Value.DomRef.Name); } case GV_DATETIME: { if (Value.Date && v.Value.Date) { return Value.Date->Compare(v.Value.Date) == 0; } break; } case GV_DOM: return Value.Dom == v.Value.Dom; case GV_OPERATOR: return Value.Op == v.Value.Op; case GV_CUSTOM: return Value.Custom == v.Value.Custom; case GV_LSURFACE: return Value.Surface.Ptr == v.Value.Surface.Ptr; case GV_GVIEW: return Value.View == v.Value.View; /* case GV_GFILE: return Value.File.Ptr == v.Value.File.Ptr; */ case GV_STREAM: return Value.Stream.Ptr == v.Value.Stream.Ptr; case GV_LMOUSE: return Value.Mouse == v.Value.Mouse; case GV_LKEY: return Value.Key == v.Value.Key; case GV_VOID_PTR: return Value.Ptr == v.Value.Ptr; case GV_HASHTABLE: { LAssert(0); break; } } return false; } LVariant &LVariant::operator =(const LDateTime *d) { Empty(); if (d) { Type = GV_DATETIME; Value.Date = new LDateTime; if (Value.Date) { *Value.Date = *d; // if (Dirty) *Dirty = true; } } return *this; } LVariant &LVariant::operator =(bool i) { Empty(); Type = GV_BOOL; Value.Bool = i; // if (Dirty) *Dirty = true; return *this; } #if LVARIANT_SIZET LVariant &LVariant::operator =(size_t i) { Empty(); #if LGI_64BIT Type = GV_INT64; Value.Int64 = i; #else Type = GV_INT32; Value.Int = i; #endif return *this; } #endif #if LVARIANT_SSIZET LVariant &LVariant::operator =(ssize_t i) { Empty(); #if LGI_64BIT Type = GV_INT64; Value.Int64 = i; #else Type = GV_INT32; Value.Int = i; #endif return *this; } #endif LVariant &LVariant::operator =(int32 i) { Empty(); Type = GV_INT32; Value.Int = i; return *this; } LVariant &LVariant::operator =(uint32_t i) { Empty(); Type = GV_INT32; Value.Int = i; return *this; } LVariant &LVariant::operator =(int64_t i) { Empty(); Type = GV_INT64; Value.Int64 = i; return *this; } LVariant &LVariant::operator =(uint64_t i) { Empty(); Type = GV_INT64; Value.Int64 = i; return *this; } LVariant &LVariant::operator =(double i) { Empty(); Type = GV_DOUBLE; Value.Dbl = i; // if (Dirty) *Dirty = true; return *this; } LVariant &LVariant::operator =(const char *s) { Empty(); if (s) { Type = GV_STRING; Value.String = NewStr(s); } return *this; } LVariant &LVariant::operator =(const char16 *s) { Empty(); if (s) { Type = GV_WSTRING; Value.WString = NewStrW(s); } // if (Dirty) *Dirty = true; return *this; } LVariant &LVariant::operator =(void *p) { Empty(); if (p) { Type = GV_VOID_PTR; Value.Ptr = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LDom *p) { Empty(); if (p) { Type = GV_DOM; Value.Dom = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LView *p) { Empty(); if (p) { Type = GV_GVIEW; Value.View = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LMouse *p) { Empty(); if (p) { Type = GV_LMOUSE; Value.Mouse = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LKey *p) { Empty(); if (p) { Type = GV_LKEY; Value.Key = p; } return *this; } LVariant &LVariant::operator =(LStream *s) { Empty(); if (s) { Type = GV_STREAM; Value.Stream.Ptr = s; Value.Stream.Own = false; } return *this; } LVariant &LVariant::operator =(LVariant const &i) { if (&i == this) return *this; Empty(); Type = i.Type; switch (Type) { case GV_NULL: { break; } case GV_INT32: { Value.Int = i.Value.Int; break; } case GV_BOOL: { Value.Bool = i.Value.Bool; break; } case GV_INT64: { Value.Int64 = i.Value.Int64; break; } case GV_DOUBLE: { Value.Dbl = i.Value.Dbl; break; } case GV_STRING: { Value.String = NewStr(((LVariant&)i).Str()); break; } case GV_WSTRING: { Value.WString = NewStrW(i.Value.WString); break; } case GV_BINARY: { SetBinary(i.Value.Binary.Length, i.Value.Binary.Data); break; } case GV_LIST: { SetList(i.Value.Lst); break; } case GV_DOM: { Value.Dom = i.Value.Dom; break; } case GV_VOID_PTR: case GV_GVIEW: case GV_LMOUSE: case GV_LKEY: { Value.Ptr = i.Value.Ptr; break; } case GV_DATETIME: { if (i.Value.Date) { Value.Date = new LDateTime; if (Value.Date) { *Value.Date = *i.Value.Date; } } break; } case GV_HASHTABLE: { if ((Value.Hash = new LHash)) { if (i.Value.Hash) { // const char *k; // for (LVariant *var = i.Value.Hash->First(&k); var; var = i.Value.Hash->Next(&k)) for (auto it : *i.Value.Hash) { Value.Hash->Add(it.key, new LVariant(*it.value)); } } } break; } case GV_CUSTOM: { Value.Custom.Data = i.Value.Custom.Data; Value.Custom.Dom = i.Value.Custom.Dom; break; } case GV_LSURFACE: { Value.Surface = i.Value.Surface; if (Value.Surface.Own && Value.Surface.Ptr) Value.Surface.Ptr->IncRef(); break; } /* case GV_GFILE: { Value.File = i.Value.File; if (Value.File.Own && Value.File.Ptr) Value.File.Ptr->AddRef(); break; } */ case GV_STREAM: { Value.Stream.Ptr = i.Value.Stream.Ptr; Value.Stream.Own = false; break; } default: { printf("%s:%i - Unknown variant type '%i'\n", _FL, Type); LAssert(0); break; } } // if (Dirty) *Dirty = true; return *this; } bool LVariant::SetDomRef(LDom *obj, char *name) { Empty(); Type = GV_DOMREF; Value.DomRef.Dom = obj; Value.DomRef.Name = NewStr(name); return Value.DomRef.Name != 0; } bool LVariant::SetBinary(ssize_t Len, void *Data, bool Own) { bool Status = false; Empty(); Type = GV_BINARY; Value.Binary.Length = Len; if (Own) { Value.Binary.Data = Data; Status = true; } else { if ((Value.Binary.Data = new uchar[Value.Binary.Length])) { if (Data) memcpy(Value.Binary.Data, Data, Value.Binary.Length); else memset(Value.Binary.Data, 0, Value.Binary.Length); Status = true; } } return Status; } -bool LVariant::SetList(List *Lst) +List *LVariant::SetList(List *Lst) { Empty(); Type = GV_LIST; if ((Value.Lst = new List) && Lst) { for (auto s: *Lst) { LVariant *New = new LVariant; if (New) { *New = *s; Value.Lst->Insert(New); } } } - return Value.Lst != 0; + return Value.Lst; } bool LVariant::SetHashTable(LHash *Table, bool Copy) { Empty(); Type = GV_HASHTABLE; if (Copy && Table) { if ((Value.Hash = new LHash)) { // const char *k; // for (LVariant *p = Table->First(&k); p; p = Table->Next(&k)) for (auto i : *Table) { Value.Hash->Add(i.key, i.value); } } } else { Value.Hash = Table ? Table : new LHash; } return Value.Hash != 0; } bool LVariant::SetSurface(class LSurface *Ptr, bool Own) { Empty(); if (!Ptr) return false; Type = GV_LSURFACE; Value.Surface.Ptr = Ptr; if ((Value.Surface.Own = Own)) Value.Surface.Ptr->IncRef(); return true; } bool LVariant::SetStream(class LStreamI *Ptr, bool Own) { Empty(); if (!Ptr) return false; Type = GV_STREAM; Value.Stream.Ptr = Ptr; Value.Stream.Own = Own; return true; } bool LVariant::OwnStr(char *s) { Empty(); if (!s) return false; Type = GV_STRING; Value.String = s; return true; } bool LVariant::OwnStr(char16 *w) { Empty(); if (!w) return false; Type = GV_WSTRING; Value.WString = w; return true; } char *LVariant::ReleaseStr() { char *Ret = Str(); if (Ret) { Value.String = 0; Type = GV_NULL; } return Ret; } LString LVariant::LStr() { return Str(); } char *LVariant::Str() { if (Type == GV_STRING) return Value.String; if (Type == GV_WSTRING) { char *u = WideToUtf8(Value.WString); DeleteArray(Value.WString); Type = GV_STRING; return Value.String = u; } return 0; } char16 *LVariant::ReleaseWStr() { char16 *Ret = WStr(); if (Ret) { Value.WString = 0; Type = GV_NULL; } return Ret; } char16 *LVariant::WStr() { if (Type == GV_WSTRING) return Value.WString; if (Type == GV_STRING) { char16 *w = Utf8ToWide(Value.String); DeleteArray(Value.String); Type = GV_WSTRING; return Value.WString = w; } return 0; } void LVariant::Empty() { switch (Type) { default: break; case GV_CUSTOM: { Value.Custom.Data = 0; Value.Custom.Dom = 0; break; } case GV_DOMREF: { DeleteArray(Value.DomRef.Name); Value.DomRef.Dom = 0; break; } case GV_STRING: { DeleteArray(Value.String); break; } case GV_WSTRING: { DeleteArray(Value.WString); break; } case GV_BINARY: { char *d = (char*) Value.Binary.Data; DeleteArray(d); Value.Binary.Data = 0; break; } case GV_DATETIME: { DeleteObj(Value.Date); break; } case GV_LIST: { if (Value.Lst) { Value.Lst->DeleteObjects(); DeleteObj(Value.Lst); } break; } case GV_HASHTABLE: { if (Value.Hash) { // for (LVariant *v = (LVariant*) Value.Hash->First(); v; v = (LVariant*) Value.Hash->Next()) for (auto i : *Value.Hash) { DeleteObj(i.value); } DeleteObj(Value.Hash); } break; } case GV_LSURFACE: { if (Value.Surface.Own && Value.Surface.Ptr) { Value.Surface.Ptr->DecRef(); Value.Surface.Ptr = NULL; } break; } /* case GV_GFILE: { if (Value.File.Ptr && Value.File.Own) { Value.File.Ptr->DecRef(); Value.File.Ptr = NULL; } break; } */ case GV_STREAM: { if (Value.Stream.Ptr) { if (Value.Stream.Own) delete Value.Stream.Ptr; Value.Stream.Ptr = NULL; } break; } } Type = GV_NULL; ZeroObj(Value); } int64 LVariant::Length() { switch (Type) { case GV_INT32: return sizeof(Value.Int); case GV_INT64: return sizeof(Value.Int64); case GV_BOOL: return sizeof(Value.Bool); case GV_DOUBLE: return sizeof(Value.Dbl); case GV_STRING: return Value.String ? strlen(Value.String) : 0; case GV_BINARY: return Value.Binary.Length; case GV_LIST: { int64 Sz = 0; if (Value.Lst) { for (auto v : *Value.Lst) Sz += v->Length(); } return Sz; } case GV_DOM: { LVariant v; if (Value.Dom) Value.Dom->GetValue("length", v); return v.CastInt32(); } case GV_DOMREF: break; case GV_VOID_PTR: return sizeof(Value.Ptr); case GV_DATETIME: return sizeof(*Value.Date); case GV_HASHTABLE: { int64 Sz = 0; if (Value.Hash) { // for (LVariant *v=Value.Hash->First(); v; v=Value.Hash->Next()) for (auto i : *Value.Hash) Sz += i.value->Length(); } return Sz; } case GV_OPERATOR: return sizeof(Value.Op); case GV_CUSTOM: break; case GV_WSTRING: return Value.WString ? StrlenW(Value.WString) * sizeof(char16) : 0; case GV_LSURFACE: { int64 Sz = 0; if (Value.Surface.Ptr) { LRect r = Value.Surface.Ptr->Bounds(); int Bytes = Value.Surface.Ptr->GetBits() >> 3; Sz = r.X() * r.Y() * Bytes; } return Sz; } case GV_GVIEW: return sizeof(LView); case GV_LMOUSE: return sizeof(LMouse); case GV_LKEY: return sizeof(LKey); case GV_STREAM: return Value.Stream.Ptr->GetSize(); default: break; } return 0; } bool LVariant::IsInt() { return Type == GV_INT32 || Type == GV_INT64; } bool LVariant::IsBool() { return Type == GV_BOOL; } bool LVariant::IsDouble() { return Type == GV_DOUBLE; } bool LVariant::IsString() { return Type == GV_STRING; } bool LVariant::IsBinary() { return Type == GV_BINARY; } bool LVariant::IsNull() { return Type == GV_NULL; } #define IsList() (Type == GV_LIST && Value.Lst) LVariant &LVariant::Cast(LVariantType NewType) { if (NewType != Type) { switch (NewType) { default: { // No conversion possible break; } case GV_INT32: { *this = (int)CastInt32(); break; } case GV_INT64: { *this = (int64_t) CastInt64(); break; } case GV_BOOL: { if (Type == GV_DOUBLE) { *this = Value.Dbl != 0.0; } else { *this = CastInt32() != 0; } break; } case GV_DOUBLE: { *this = CastDouble(); break; } case GV_STRING: { *this = CastString(); break; } case GV_DATETIME: { switch (Type) { case GV_STRING: { // String -> LDateTime LDateTime *Dt = new LDateTime; if (Dt) { Dt->Set(Value.String); Empty(); Value.Date = Dt; Type = NewType; } break; } case GV_INT64: { // Int64 (system date) -> LDateTime LDateTime *Dt = new LDateTime; if (Dt) { Dt->Set((uint64_t)Value.Int64); Empty(); Value.Date = Dt; Type = NewType; } break; } default: { // No conversion available break; } } break; } } } return *this; } void *LVariant::CastVoidPtr() const { switch (Type) { default: break; case GV_STRING: return Value.String; case GV_BINARY: return Value.Binary.Data; case GV_LIST: return Value.Lst; case GV_DOM: return Value.Dom; case GV_DOMREF: return Value.DomRef.Dom; case GV_VOID_PTR: return Value.Ptr; case GV_DATETIME: return Value.Date; case GV_HASHTABLE: return Value.Hash; case GV_CUSTOM: return Value.Custom.Data; case GV_WSTRING: return Value.WString; case GV_LSURFACE: return Value.Surface.Ptr; case GV_GVIEW: return Value.View; case GV_LMOUSE: return Value.Mouse; case GV_LKEY: return Value.Key; } return 0; } LDom *LVariant::CastDom() const { switch (Type) { default: break; case GV_DOM: return Value.Dom; case GV_DOMREF: return Value.DomRef.Dom; case GV_STREAM: return dynamic_cast(Value.Stream.Ptr); case GV_LSURFACE: return Value.Surface.Ptr; case GV_CUSTOM: return Value.Custom.Dom; } return NULL; } bool LVariant::CastBool() const { switch (Type) { default: LAssert(0); break; case GV_NULL: return false; case GV_INT32: return Value.Int != 0; case GV_INT64: return Value.Int64 != 0; case GV_BOOL: return Value.Bool; case GV_DOUBLE: return Value.Dbl != 0.0; case GV_BINARY: return Value.Binary.Data != NULL; case GV_LIST: return Value.Lst != NULL; case GV_DOM: return Value.Dom != NULL; case GV_DOMREF: return Value.DomRef.Dom != NULL; case GV_VOID_PTR: return Value.Ptr != NULL; case GV_GVIEW: return Value.View != NULL; case GV_LMOUSE: return Value.Mouse != NULL; case GV_LKEY: return Value.Key != NULL; case GV_DATETIME: return Value.Date != NULL; case GV_HASHTABLE: return Value.Hash != NULL; case GV_OPERATOR: return Value.Op != OpNull; case GV_CUSTOM: return Value.Custom.Dom != 0 && Value.Custom.Data != 0; /* case GV_GFILE: return Value.File.Ptr != NULL; */ case GV_STREAM: return Value.Stream.Ptr != NULL; // As far as I understand this is the behavour in Python, which I'm using for // a reference to what the "correct" thing to do here is. Basically it's treating // the string like a pointer instead of a value. If the pointer is valid the // conversion to bool return true, and false if it's not a valid pointer. This // means things like if (!StrinLVariant) evaluate correctly in the scripting engine // but it means that if you want to evaluate the value of the varient you should // use CastInt32 instead. case GV_STRING: return ValidStr(Value.String); case GV_WSTRING: return ValidStrW(Value.WString); } return false; } double LVariant::CastDouble() const { switch (Type) { default: break; case GV_BOOL: return Value.Bool ? 1.0 : 0.0; case GV_DOUBLE: return Value.Dbl; case GV_INT32: return (double)Value.Int; case GV_INT64: return (double)Value.Int64; case GV_STRING: return Value.String ? atof(Value.String) : 0; case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastDouble(); } } break; } } return 0; } int32 LVariant::CastInt32() const { switch (Type) { default: break; case GV_BOOL: return (int32)Value.Bool; case GV_DOUBLE: return (int32)Value.Dbl; case GV_INT32: return Value.Int; case GV_INT64: return (int32)Value.Int64; case GV_STRING: if (!Value.String) return 0; if (IsAlpha(Value.String[0])) return !Stricmp(Value.String, "true"); else if (Value.String[0] == '0' && tolower(Value.String[1]) == 'x') return static_cast(Atoi(Value.String, 16)); return atoi(Value.String); case GV_DOM: return Value.Dom != 0; case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastInt32(); } } break; } case GV_LIST: return Value.Lst != NULL; case GV_HASHTABLE: return Value.Hash != NULL; case GV_LSURFACE: return Value.Surface.Ptr != NULL; case GV_GVIEW: return Value.View != NULL; case GV_LMOUSE: return Value.Mouse != NULL; case GV_LKEY: return Value.Key != NULL; case GV_STREAM: return Value.Stream.Ptr != NULL; } return 0; } int64 LVariant::CastInt64() const { switch (Type) { default: break; case GV_BOOL: return (int64)Value.Bool; case GV_DOUBLE: return (int64)Value.Dbl; case GV_INT32: return Value.Int; case GV_INT64: return Value.Int64; case GV_STRING: { if (!Value.String) return 0; if (IsAlpha(Value.String[0])) return !Stricmp(Value.String, "true"); else if (Value.String[0] == '0' && tolower(Value.String[1]) == 'x') return Atoi(Value.String, 16); return Atoi(Value.String); } case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastInt64(); } } break; } } return 0; } char *LVariant::CastString() { char i[40]; switch (Type) { case GV_LIST: { LStringPipe p(256); List::I it = Value.Lst->begin(); bool First = true; p.Print("{"); for (LVariant *v = *it; v; v = *++it) { if (v->Type == GV_STRING || v->Type == GV_WSTRING) p.Print("%s\"%s\"", First ? "" : ", ", v->CastString()); else p.Print("%s%s", First ? "" : ", ", v->CastString()); First = false; } p.Print("}"); OwnStr(p.NewStr()); return Str(); break; } case GV_HASHTABLE: { LStringPipe p(256); p.Print("{"); bool First = true; // const char *k; // for (LVariant *v = Value.Hash->First(&k); v; v = Value.Hash->Next(&k)) for (auto i : *Value.Hash) { p.Print("%s%s = %s", First ? "" : ", ", i.key, i.value->CastString()); First = false; } p.Print("}"); OwnStr(p.NewStr()); return Str(); break; } case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastString(); } } break; } case GV_INT32: { sprintf_s(i, sizeof(i), "%i", Value.Int); *this = i; return Str(); } case GV_DOUBLE: { sprintf_s(i, sizeof(i), "%f", Value.Dbl); *this = i; return Str(); } case GV_BOOL: { sprintf_s(i, sizeof(i), "%i", Value.Bool); *this = i; return Str(); } case GV_INT64: { sprintf_s(i, sizeof(i), LPrintfInt64, Value.Int64); *this = i; return Str(); } case GV_STRING: case GV_WSTRING: { return Str(); } case GV_DATETIME: { if (Value.Date) { char s[64]; Value.Date->Get(s, sizeof(s)); *this = s; return Str(); } break; } case GV_DOM: { sprintf_s(i, sizeof(i), "dom:%p", Value.Dom); *this = i; break; } default: { break; } } return 0; } ///////////////////////////////////////////////////////////////////////////////// LDom *LDom::ResolveObject(const char *Var, LString &Name, LString &Array) { LDom *Object = this; try { // Tokenize the string LString::Array t; for (auto *s = Var; s && *s; ) { const char *e = s; while (*e && *e != '.') { if (*e == '[') { e++; while (*e && *e != ']') { if (*e == '\"' || *e == '\'') { char d = *e++; while (*e && *e != d) e++; if (*e == d) e++; } else e++; } if (*e == ']') e++; } else e++; } LString part = LString(s, e - s).Strip(); if (part.Length() > 0) t.New() = part; // Store non-empty part s = *e ? e + 1 : e; } // Process elements for (int i=0; iGetVariant(Obj, v, Index)) { if (v.Type == GV_LIST) { int N = atoi(Index); LVariant *Element = v.Value.Lst->ItemAt(N); if (Element && Element->Type == GV_DOM) { Object = Element->Value.Dom; } else { return NULL; } } else if (v.Type == GV_DOM) { Object = v.Value.Dom; } else { return NULL; } } else { return NULL; } } else { if (Object->GetVariant(Obj, v) && v.Type == GV_DOM) { Object = v.Value.Dom; } else { return NULL; } } } } } catch (...) { LgiTrace("LDom::ResolveObject crashed: '%s'\n", Var); return NULL; } return Object; } struct LDomPropMap { LHashTbl, LDomProperty> ToProp; LHashTbl, const char *> ToString; LDomPropMap() { #undef _ #define _(symbol, txt) Define(txt, symbol); #include "lgi/common/DomFields.h" #undef _ } void Define(const char *s, LDomProperty p) { if (!s) return; #if defined(_DEBUG) // Check for duplicates. auto existing_prop = ToProp.Find(s); LAssert(existing_prop == ObjNone); auto existing_str = ToString.Find(p); LAssert(existing_str == NULL); #endif ToProp.Add(s, p); ToString.Add(p, s); } } DomPropMap; LDomProperty LStringToDomProp(const char *Str) { return DomPropMap.ToProp.Find(Str); } const char *LDomPropToString(LDomProperty Prop) { return DomPropMap.ToString.Find(Prop); } bool LDom::GetValue(const char *Var, LVariant &Value) { if (!Var) return false; if (!_OnAccess(true)) { LgiTrace("%s:%i - Locking error\n", _FL); LAssert(0); return false; } bool Status = false; LString Name, Arr; LDom *Object = ResolveObject(Var, Name, Arr); if (Object) { if (Name.IsEmpty()) LgiTrace("%s:%i - Warning name parse failed for '%s'\n", _FL, Var); else Status = Object->GetVariant(Name, Value, Arr.IsEmpty() ? NULL : Arr); } _OnAccess(false); return Status; } bool LDom::SetValue(const char *Var, LVariant &Value) { bool Status = false; if (Var) { // LMutex *Sem = dynamic_cast(this); if (_OnAccess(true)) { LString Name, Arr; LDom *Object = ResolveObject(Var, Name, Arr); if (Object) { if (Name.IsEmpty()) LgiTrace("%s:%i - Warning name parse failed for '%s'\n", _FL, Var); else Status = Object->SetVariant(Name, Value, Arr.IsEmpty() ? NULL : Arr); } _OnAccess(false); } else { LgiTrace("%s:%i - Locking error\n", _FL); LAssert(0); } } return Status; } bool LVariant::Add(LVariant *v, int Where) { if (!v) { LAssert(!"No value to insert."); return false; } if (Type == GV_NULL) SetList(); if (Type != GV_LIST) { LAssert(!"Not a list variant"); return false; } return Value.Lst->Insert(v, Where); } LString LVariant::ToString() { LString s; switch (Type) { case GV_NULL: s = "NULL"; break; case GV_INT32: s.Printf("(int)%i", Value.Int); break; case GV_INT64: s.Printf("(int64)" LPrintfInt64, Value.Int64); break; case GV_BOOL: s.Printf("(bool)%s", Value.Bool ? "true" : "false"); break; case GV_DOUBLE: s.Printf("(double)%f", Value.Dbl); break; case GV_STRING: s.Printf("(string)\"%s\"", Value.String); break; case GV_BINARY: s.Printf("(binary[%i])%p", Value.Binary.Length, Value.Binary.Data); break; case GV_LIST: s.Printf("(list[%i])%p", Value.Lst?Value.Lst->Length():0, Value.Lst); break; case GV_DOM: s.Printf("(dom)%p", Value.Dom); break; case GV_DOMREF: s.Printf("(dom)%p.%s", Value.DomRef.Dom, Value.DomRef.Name); break; case GV_VOID_PTR: s.Printf("(void*)%p", Value.Ptr); break; case GV_DATETIME: { char dt[64]; Value.Date->Get(dt, sizeof(dt)); s.Printf("(datetime)%s", dt); break; } case GV_HASHTABLE: s.Printf("(hashtbl)%p", Value.Hash); break; case GV_OPERATOR: s.Printf("(operator)%s", OperatorToString(Value.Op)); break; case GV_CUSTOM: s.Printf("(custom.%s)%p", Value.Custom.Dom->GetName(), Value.Custom.Data); break; case GV_WSTRING: s.Printf("(wstring)\"%S\"", Value.WString); break; case GV_LSURFACE: s.Printf("(gsurface)%p", Value.Surface.Ptr); break; case GV_GVIEW: s.Printf("(gview)%p", Value.View); break; case GV_LMOUSE: s.Printf("(gmouse)%p", Value.Mouse); break; case GV_LKEY: s.Printf("(gkey)%p", Value.Key); break; case GV_STREAM: s.Printf("(stream)%p", Value.Stream.Ptr); break; default: s = "(unknown)NULL"; break; } return s; } ///////////////////////////////////////////////////////////////////////////////////////////////////// LCustomType::LCustomType(const char *name, int pack) : FldMap(0, -1) { Name = name; Pack = 1; Size = 0; } LCustomType::LCustomType(const char16 *name, int pack) : FldMap(0, -1) { Name = name; Pack = 1; Size = 0; } LCustomType::~LCustomType() { Flds.DeleteObjects(); Methods.DeleteObjects(); } size_t LCustomType::Sizeof() { return (size_t)PadSize(); } ssize_t LCustomType::PadSize() { if (Pack > 1) { // Bump size to the pack boundary... int Remain = Size % Pack; if (Remain) return Size + Pack - Remain; } return Size; } int LCustomType::IndexOf(const char *Field) { return FldMap.Find(Field); } int LCustomType::AddressOf(const char *Field) { if (!Field) return -1; for (unsigned i=0; iName, Field)) return (int)i; } return -1; } bool LCustomType::DefineField(const char *Name, LCustomType *Type, int ArrayLen) { if (ArrayLen < 1) { LAssert(!"Can't have zero size field."); return false; } if (Name == NULL || Type == NULL) { LAssert(!"Invalid parameter."); return false; } if (FldMap.Find(Name) >= 0) { LAssert(!"Field already exists."); return false; } FldMap.Add(Name, (int)Flds.Length()); CustomField *Def; Flds.Add(Def = new CustomField); Size = PadSize(); Def->Offset = Size; Def->Name = Name; Def->Type = GV_CUSTOM; Def->Bytes = Type->Sizeof(); Def->ArrayLen = ArrayLen; Size += Def->Bytes * ArrayLen; return true; } bool LCustomType::DefineField(const char *Name, LVariantType Type, int Bytes, int ArrayLen) { if (ArrayLen < 1) { LAssert(!"Can't have zero size field."); return false; } if (Name == NULL) { LAssert(!"No field name."); return false; } if (FldMap.Find(Name) >= 0) { LAssert(!"Field already exists."); return false; } FldMap.Add(Name, (int)Flds.Length()); CustomField *Def; Flds.Add(Def = new CustomField); Size = PadSize(); Def->Offset = Size; Def->Name = Name; Def->Type = Type; Def->Bytes = Bytes; Def->ArrayLen = ArrayLen; Size += Bytes * ArrayLen; return true; } LCustomType::Method *LCustomType::GetMethod(const char *Name) { return MethodMap.Find(Name); } LCustomType::Method *LCustomType::DefineMethod(const char *Name, LArray &Params, size_t Address) { Method *m = MethodMap.Find(Name); if (m) { LAssert(!"Method already defined."); return NULL; } Methods.Add(m = new Method); m->Name = Name; m->Params = Params; m->Address = Address; MethodMap.Add(Name, m); return m; } bool LCustomType::CustomField::GetVariant(const char *Field, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Field); switch (p) { case ObjName: // Type: String Value = Name; break; case ObjLength: // Type: Int32 Value = Bytes; break; default: return false; } return true; } ssize_t LCustomType::CustomField::Sizeof() { switch (Type) { case GV_INT32: return sizeof(int32); case GV_INT64: return sizeof(int64); case GV_BOOL: return sizeof(bool); case GV_DOUBLE: return sizeof(double); case GV_STRING: return sizeof(char); case GV_DATETIME: return sizeof(LDateTime); case GV_HASHTABLE: return sizeof(LVariant::LHash); case GV_OPERATOR: return sizeof(LOperator); case GV_LMOUSE: return sizeof(LMouse); case GV_LKEY: return sizeof(LKey); case GV_CUSTOM: return Nested->Sizeof(); default: LAssert(!"Unknown type."); break; } return 0; } bool LCustomType::Get(int Index, LVariant &Out, uint8_t *This, int ArrayIndex) { if (Index < 0 || Index >= Flds.Length() || !This) { LAssert(!"Invalid parameter error."); return false; } CustomField *Def = Flds[Index]; if (ArrayIndex < 0 || ArrayIndex >= Def->ArrayLen) { LAssert(!"Array out of bounds."); return false; } uint8_t *Ptr = This + Def->Offset; Out.Empty(); switch (Def->Type) { case GV_STRING: { int Len; for (Len = 0; Ptr[Len] && Len < Def->ArrayLen-1; Len++) ; Out.OwnStr(NewStr((char*)Ptr, Len)); break; } case GV_WSTRING: { char16 *p = (char16*)Ptr; int Len; for (Len = 0; p[Len] && Len < Def->ArrayLen-1; Len++) ; Out.OwnStr(NewStrW(p, Len)); break; } case GV_INT32: case GV_INT64: { switch (Def->Bytes) { case 1: { Out.Value.Int = Ptr[ArrayIndex]; Out.Type = GV_INT32; break; } case 2: { Out.Value.Int = ((uint16*)Ptr)[ArrayIndex]; Out.Type = GV_INT32; break; } case 4: { Out.Value.Int = ((uint32_t*)Ptr)[ArrayIndex]; Out.Type = GV_INT32; break; } case 8: { Out.Value.Int64 = ((uint64*)Ptr)[ArrayIndex]; Out.Type = GV_INT64; break; } default: { LAssert(!"Unknown integer size."); return false; } } break; } case GV_MAX: { Out = *((LVariant*)Ptr); break; } default: { LAssert(!"Impl this type."); return false; } } return true; } bool LCustomType::Set(int Index, LVariant &In, uint8_t *This, int ArrayIndex) { if (Index < 0 || Index >= Flds.Length() || !This) { LAssert(!"Invalid parameter error."); return false; } CustomField *Def = Flds[Index]; uint8_t *Ptr = This + Def->Offset; if (ArrayIndex < 0 || ArrayIndex >= Def->ArrayLen) { LAssert(!"Array out of bounds."); return false; } switch (Def->Type) { case GV_STRING: { char *s = In.Str(); if (!s) { *Ptr = 0; break; } if (Def->Bytes == 1) { // Straight up string copy... if (s) strcpy_s((char*)Ptr, Def->ArrayLen, s); else *Ptr = 0; } else if (Def->Bytes == sizeof(char16)) { // utf8 -> wide conversion... const void *In = Ptr; ssize_t Len = strlen(s); ssize_t Ch = LBufConvertCp(Ptr, LGI_WideCharset, Def->ArrayLen-1, In, "utf-8", Len); if (Ch >= 0) { // Null terminate Ptr[Ch] = 0; } else { LAssert(!"LBufConvertCp failed."); return false; } } break; } case GV_WSTRING: { char16 *p = (char16*)Ptr; char16 *w = In.WStr(); if (!w) { *p = 0; break; } if (Def->Bytes == sizeof(char16)) { // Straight string copy... Strcpy(p, Def->ArrayLen, w); } else { // Conversion to utf-8 const void *In = Ptr; ssize_t Len = StrlenW(w) * sizeof(char16); ssize_t Ch = LBufConvertCp(Ptr, "utf-8", Def->ArrayLen-sizeof(char16), In, LGI_WideCharset, Len); if (Ch >= 0) { // Null terminate p[Ch/sizeof(char16)] = 0; } else { LAssert(!"LBufConvertCp failed."); return false; } } break; } case GV_INT32: case GV_INT64: { switch (Def->Bytes) { case 1: { Ptr[ArrayIndex] = In.CastInt32(); break; } case 2: { ((uint16*)Ptr)[ArrayIndex] = In.CastInt32(); break; } case 4: { ((uint32_t*)Ptr)[ArrayIndex] = In.CastInt32(); break; } case 8: { ((uint64*)Ptr)[ArrayIndex] = In.CastInt64(); break; } default: { LAssert(!"Unknown integer size."); return false; } } break; } case GV_MAX: { *((LVariant*)Ptr) = In; break; } default: LAssert(!"Impl this type."); break; } return true; } bool LCustomType::GetVariant(const char *Field, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Field); switch (p) { case ObjName: // Type: String { Value = Name; return true; } case ObjType: // Type: String { Value = "LCustomType"; return true; } case ObjLength: // Type: Int32 { Value = (int)Sizeof(); return true; } case ObjField: // Type: CustomField[] { if (Array) { int Index = atoi(Array); if (Index >= 0 && Index < Flds.Length()) { Value = (LDom*)&Flds[Index]; return true; } } else { Value = (int)Flds.Length(); break; } break; } default: break; } LAssert(0); return false; } bool LCustomType::SetVariant(const char *Name, LVariant &Value, const char *Array) { LAssert(0); return false; } bool LCustomType::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { if (!MethodName || !ReturnValue) return false; if (!_stricmp(MethodName, "New")) { ReturnValue->Empty(); ReturnValue->Type = GV_CUSTOM; ReturnValue->Value.Custom.Dom = this; ReturnValue->Value.Custom.Data = new uint8_t[Sizeof()]; return true; } if (!_stricmp(MethodName, "Delete")) // Type: (Object) { for (unsigned i=0; iType == GV_CUSTOM) { DeleteArray(v->Value.Custom.Data); v->Empty(); } } return true; } LAssert(0); return false; }