unit frmLuaEngineUnit; {$mode delphi} interface uses windows, Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls, Menus, ExtCtrls, SynMemo, SynCompletion, SynEdit, lua, lauxlib, lualib, LuaSyntax, luahandler, cefuncproc, strutils, InterfaceBase, ComCtrls, SynGutterBase, SynEditMarks, PopupNotifier, ActnList, SynEditHighlighter, AvgLvlTree, math; type { TfrmLuaEngine } TfrmLuaEngine = class(TForm) btnExecute: TButton; FindDialog1: TFindDialog; GroupBox1: TGroupBox; MenuItem12: TMenuItem; MenuItem13: TMenuItem; miResizeOutput: TMenuItem; miSetBreakpoint: TMenuItem; miRun: TMenuItem; miSingleStep: TMenuItem; ToolButton1: TToolButton; tbStopDebug: TToolButton; tShowHint: TIdleTimer; ilLuaDebug: TImageList; ilSyneditDebug: TImageList; MainMenu1: TMainMenu; MenuItem10: TMenuItem; MenuItem11: TMenuItem; MenuItem4: TMenuItem; MenuItem5: TMenuItem; MenuItem6: TMenuItem; miView: TMenuItem; cbShowOnPrint: TMenuItem; MenuItem7: TMenuItem; MenuItem8: TMenuItem; MenuItem9: TMenuItem; mOutput: TMemo; MenuItem1: TMenuItem; MenuItem2: TMenuItem; MenuItem3: TMenuItem; mScript: TSynEdit; OpenDialog1: TOpenDialog; Panel1: TPanel; Panel2: TPanel; Panel3: TPanel; pmEditor: TPopupMenu; dlgReplace: TReplaceDialog; SaveDialog1: TSaveDialog; Splitter1: TSplitter; tbDebug: TToolBar; tbRun: TToolButton; tbSingleStep: TToolButton; procedure btnExecuteClick(Sender: TObject); procedure Button1Click(Sender: TObject); procedure dlgReplaceFind(Sender: TObject); procedure dlgReplaceReplace(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormShow(Sender: TObject); procedure MenuItem10Click(Sender: TObject); procedure MenuItem11Click(Sender: TObject); procedure MenuItem13Click(Sender: TObject); procedure MenuItem2Click(Sender: TObject); procedure MenuItem3Click(Sender: TObject); procedure MenuItem5Click(Sender: TObject); procedure MenuItem6Click(Sender: TObject); procedure MenuItem7Click(Sender: TObject); procedure MenuItem8Click(Sender: TObject); procedure MenuItem9Click(Sender: TObject); procedure miResizeOutputClick(Sender: TObject); procedure miSetBreakpointClick(Sender: TObject); procedure mScriptChange(Sender: TObject); procedure mScriptGutterClick(Sender: TObject; X, Y, Line: integer; mark: TSynEditMark); procedure mScriptKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState ); procedure mScriptMouseEnter(Sender: TObject); procedure mScriptMouseLeave(Sender: TObject); procedure mScriptMouseLink(Sender: TObject; X, Y: Integer; var AllowMouseLink: Boolean); procedure mScriptMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); procedure mScriptShowHint(Sender: TObject; HintInfo: PHintInfo); procedure Panel2Resize(Sender: TObject); procedure tbRunClick(Sender: TObject); procedure tbSingleStepClick(Sender: TObject); procedure tbStopDebugClick(Sender: TObject); procedure tShowHintTimer(Sender: TObject); private { private declarations } hintwindow:THintWindow; continuemethod: integer; public { public declarations } synhighlighter: TSynLuaSyn; end; var frmLuaEngine: TfrmLuaEngine; implementation { TfrmLuaEngine } uses luaclass, SynEditTypes; resourcestring rsError = 'Script Error'; rsLEErrorInLine = 'Error in line '; rsLEUndefinedError = 'Undefined error'; rsLEOnlyOneScriptCanBeDebuggedAtATimeEtc = 'Only one script can be debugged at a time. Continue executing this script without the debugger?'; rsLEUserClickedStop = 'User clicked stop'; var LuaDebugForm: TfrmLuaEngine; LuaDebugSingleStepping: boolean; LuaDebugInfo: Plua_Debug; LuaDebugVariables: TStringToStringTree; LuaDebugSource: pointer; procedure TfrmLuaEngine.Panel2Resize(Sender: TObject); begin btnexecute.Height:=panel2.clientheight-(2*btnexecute.top); end; procedure TfrmLuaEngine.tbRunClick(Sender: TObject); begin if tbdebug.visible and tbrun.enabled and tbrun.visible then begin continuemethod:=1; tbDebug.enabled:=false; tbRun.enabled:=false; tbSingleStep.enabled:=false; tbStopDebug.enabled:=false; end else begin if btnExecute.enabled then btnExecute.click; end; end; procedure TfrmLuaEngine.tbSingleStepClick(Sender: TObject); begin if tbdebug.visible and tbSingleStep.Enabled and tbSingleStep.Visible then begin continuemethod:=2; tbDebug.enabled:=false; tbRun.enabled:=false; tbSingleStep.enabled:=false; tbStopDebug.enabled:=false; end; end; procedure TfrmLuaEngine.tbStopDebugClick(Sender: TObject); begin if tbdebug.visible and tbStopDebug.Enabled and tbStopDebug.Visible then begin continuemethod:=3; tbDebug.enabled:=false; tbRun.enabled:=false; tbSingleStep.enabled:=false; tbStopDebug.enabled:=false; end; end; function findToken(s: string; var start: integer):string; var i: integer; begin for i:=start to length(s) do begin if (inil then begin if (name=token) then begin result:=true; break; //leave the value on the stack end else begin lua_pop(LuaVM, 1); inc(i); end; end; until name=nil; if result=false then begin //try global lua_getglobal(luavm, pchar(token)); if lua_isnil(LuaVM,-1) then lua_pop(luavm, 1) else result:=true; //keep this object in the stack end; if not result then exit; //nothing found while indexstart) do if line[stop-1] in ['a'..'z','A'..'Z','0'..'9','_','[',']','''','"','.'] then break else dec(stop); token:=copy(line,start, stop-start); if getObject(token) then begin description:=LuaValueToDescription(LuaVM, -1); lua_pop(Luavm,1); end else description:='nil'; //parse the first word and find out if it's a global or local start //token:=findToken(line, start); { if length(token)>1 then begin if token[length(token)] = '~' then //~ is not part of the token... token:=copy(token, 1, length(token)+1); end; mscript.GetWordBoundsAtRowCol(p3, start, stop); if (start>1) and (line[start-1]='.') then //check if it starts with a . and if so, do table lookups using the parent(s) begin last:=start-2; for i:=start-2 downto 1 do //trace back till you find the start of the line or a seperator begin if i=1 then begin s:=copy(line, 1, last); path.Insert(0, s); end; if line[i] in ['a'..'z','A'..'Z','0'..'9','_'] then continue; if line[i]='.' then begin s:=copy(line, i+1, last-i); path.Insert(0, s); last:=i-1; end else if line[i]=']' then begin //array element encounterd end else break; //seperator encountered end; end; token:=''; description:='nil'; foundcount:=0; for i:=0 to path.Count-1 do begin if token='' then token:=path[i] else token:=token+'.'+path[i]; found:=false; if i=0 then //get it from global or local begin repeat name:=lua_getlocal(L, ar, i); if name<>nil then begin if (name=path[0]) then begin found:=true; break; end else begin lua_pop(L, 1); inc(i); end; end; until name=nil; if not found then begin //try global lua_getfield(LuaVM, LUA_GLOBALSINDEX, pchar(path[0])); if lua_isnil(LuaVM,-1) then lua_pop(luavm, 1) else found:=true; //keep this object in the stack if not found then break; end; if found then inc(foundcount); end; if foundcount>0 then begin if i=path.count-1 then begin //last one description:=LuaValueToDescription(LuaVM, -1); end else begin //get more paths lua_getfield(); end; end; //description:=LuaDebugVariables[token]; if description='' then //check if it's a global begin //look up LuaCS.Enter; try lua_getfield(LuaVM, LUA_GLOBALSINDEX, pchar(token)); if lua_isnil(LuaVM,-1) then description:='nil' else description:=LuaValueToDescription(LuaVM, -1)+' (global)'; lua_pop(luavm, 1); finally luacs.Leave; end; if description='' then begin description:=' '; LuaDebugVariables.Add(token, description); //could not be found end; end; end; path.free; } if description=' ' then begin //means nothing was found exit; end; if hintwindow=nil then hintwindow:=THintWindow.Create(self); description:=token+' = '+description; r:=hintwindow.CalcHintRect(mscript.width, description, nil); r.Top:=r.top+p.y; r.Left:=r.left+p.x; r.Right:=r.right+p.x; r.Bottom:=r.Bottom+p.y; hintwindow.ActivateHint(r, description); end; end; // mScript.ShowHint:=; end; function onerror(L: PLua_State): integer; cdecl; var ld: lua_Debug; frm: TfrmLuaEngine; r: integer; t: integer; begin //todo: Try to get this to work (might be a lua bug) { result:=0; frm:=luaclass_getClassObject(L); t:=lua_gettop(L); ZeroMemory(@ld, sizeof(ld)); r:=lua_getstack(L, 1, @ld); if r=1 then begin lua_getinfo(L, '>l', @ld); lua_pushstring(L, rsLEErrorInLine+inttostr(ld.currentline)); end else lua_pushstring(L, rsLEUndefinedError); } result:=1; end; function hasLuaBreakpoint(linenumber: integer): boolean; begin result:=LuaDebugSingleStepping or (LuaDebugForm.mScript.Marks.Line[linenumber]<>nil); end; procedure LineHook_Handler(L: Plua_State; ar: Plua_Debug); var i,j: integer; s,s2: integer; mark: TSynEditMark; name: pchar; value: string; stack: integer; begin LuaDebugForm.continuemethod:=0; if MainThreadID<>GetCurrentThreadId then begin //Only the main thread can be debugged for now exit; end; if lua_getinfo(L,'nSl', ar)<>0 then begin if LuaDebugSource=nil then LuaDebugSource:=ar.source; if (ar.source=LuaDebugSource) and (hasLuaBreakpoint(ar.currentline)) then begin //break // frmLuaEngine.visible:=false; // frmLuaEngine.ShowModal; // frmLuaEngine.show; LuaDebugForm.show; LuaDebugForm.SetFocus; if LuaDebugForm.mScript.Marks.Line[ar.currentline]<>nil then begin //update the icon for the current line if LuaDebugForm.mScript.Marks.Line[ar.currentline][0].ImageIndex = 0 then LuaDebugForm.mScript.Marks.Line[ar.currentline][0].ImageIndex:=2; end else begin mark:=TSynEditMark.Create(LuaDebugForm.mscript); mark.line:=ar.currentline; mark.ImageList:=LuaDebugForm.ilSyneditDebug; mark.ImageIndex:=1; mark.Visible:=true; LuaDebugForm.mscript.Marks.Add(mark); end; LuaDebugForm.show; //activate the debug gui LuaDebugForm.tbDebug.Visible:=true; LuaDebugForm.tbDebug.enabled:=true; LuaDebugForm.tbRun.enabled:=true; LuaDebugForm.tbSingleStep.enabled:=true; LuaDebugForm.tbStopDebug.enabled:=true; LuaDebugForm.mScript.ReadOnly:=true; LuaDebugForm.mScript.CaretY:=ar.currentline; LuaDebugForm.mScript.EnsureCursorPosVisible; LuaDebugForm.continuemethod:=0; LuaDebugInfo:=ar; LuaDebugVariables:=TStringToStringTree.Create(true); i:=1; repeat name:=lua_getlocal(L, ar, i); if name<>nil then begin if copy(name,1,1)<>'(' then //(*temporary) begin value:=LuaValueToDescription(L, -1)+' (local)'; LuaDebugVariables.Add(name, value); end; lua_pop(L, 1); inc(i); end; until name=nil; while LuaDebugForm.continuemethod=0 do begin try application.ProcessMessages; except if Application.CaptureExceptions then Application.HandleException(LuaDebugForm) else raise; end; if application.Terminated or (LuaDebugForm.Visible=false) then break; application.Idle(true); end; if application.Terminated then ExitProcess(UINT(-1)); //there's nothing to return to... LuaDebugForm.mScript.ReadOnly:=false; //clear the current instruction pointer if LuaDebugForm.mScript.Marks.Line[ar.currentline]<>nil then begin if LuaDebugForm.mScript.Marks.Line[ar.currentline][0].ImageIndex = 2 then //bp with the current bp set LuaDebugForm.mScript.Marks.Line[ar.currentline][0].ImageIndex:=0 //set back to normal bp else LuaDebugForm.mScript.Marks.Line[ar.currentline][0].Free; //clear bp end; LuaDebugSingleStepping:=false; end; // frmLuaEngine.moutput.lines.add('called:'+ar.what+' ('+inttostr(ar.currentline)+')'); end; end; procedure LineHook(L: Plua_State; ar: Plua_Debug); cdecl; begin LineHook_Handler(L, ar); case LuaDebugForm.continuemethod of 1: ;//continue (normal bp's only) 2: LuaDebugSingleStepping:=true; //single step next instruction 3: begin //lua_sethook(L, linehook, 0, 0); lua_pushstring(L, rsLEUserClickedStop); lua_error(L); end; end; end; procedure TfrmLuaEngine.btnExecuteClick(Sender: TObject); var pc: pchar; i,j: integer; oldprintoutput: Tstrings; c: tobject; err: integer; oldstack: integer; dodebug: boolean; begin dodebug:=false; for i:=0 to mScript.Marks.Count-1 do if mscript.Marks[i].ImageIndex=0 then begin dodebug:=true; break; end; if dodebug then begin if LuaDebugForm=nil then begin for i:=0 to mScript.Marks.Count-1 do if mscript.Marks[i].ImageIndex=0 then begin //this script wishes to get debugged dodebug:=true; LuaDebugForm:=self; LuaDebugSingleStepping:=false; LuaDebugSource:=nil; LuaDebugForm.btnExecute.enabled:=false; mscript.OnMouseEnter:=mScriptMouseEnter; mscript.OnMouseLeave:=mScriptMouseLeave; if LuaDebugVariables<>nil then LuaDebugVariables.Clear; break; end; end else begin dodebug:=false; if MessageDlg(rsLEOnlyOneScriptCanBeDebuggedAtATimeEtc, mtConfirmation, [mbyes, mbno], 0)<>mryes then exit; end; end; luacs.Enter; oldstack:=lua_gettop(luavm); oldprintoutput:=lua_oldprintoutput; try mOutput.lines.add(mscript.text); lua_setPrintOutput(mOutput.lines); i:=0; { luaclass_newClass(Luavm, self); lua_pushcclosure(Luavm, onerror,1); err:=lua_gettop(Luavm); if luaL_loadstring(Luavm, pchar(mScript.text))=0 then i := lua_pcall(Luavm, 0, LUA_MULTRET, err); lua_remove(luavm, err); } if dodebug then lua_sethook(luavm, linehook, LUA_MASKLINE, 0); if lua_dostring(Luavm, pchar(mScript.text))=0 then begin j:=lua_gettop(luavm); if j>oldstack then begin for i:=oldstack+1 to j do begin mOutput.lines.add(':'+LuaValueToDescription(luavm, i)); { pc:=lua_tolstring(luavm, i,nil); if pc<>nil then mOutput.lines.add(':'+pc) else begin if lua_islightuserdata(luavm,i) then //shouldn't occur anymore moutput.lines.add(':'+p->'+inttohex(ptruint(lua_touserdata(luavm,i)),1)) else if lua_isboolean(luavm,i) then moutput.lines.add(':(boolean)'+BoolToStr(lua_toboolean(Luavm, i),'true','false')) else if lua_isnil(luavm,i) then moutput.lines.add(':'+'nil') else if lua_istable(luavm, i) then moutput.lines.add(':'+'table') else if lua_isfunction(luavm,i) then moutput.lines.add(':'+'function') else if lua_isuserdata(luavm,i) then begin try c:=lua_ToCEUserData(luavm, i); moutput.lines.add(':'+'class object ('+c.ClassName+')') except moutput.lines.add(':'+'class object (corrupt)') end; end else moutput.lines.add(':'+'unknown') end;} end; end; end else begin i:=lua_gettop(luavm); if i>oldstack then begin //is currently shown inside the pcall function pc:=lua_tolstring(luavm, -1,nil); if pc<>nil then mOutput.lines.add(rsError+':'+pc) else moutput.lines.add(rsError+':'+'nil'); end else moutput.lines.add(rsError); end; finally if dodebug then begin LuaDebugForm.btnExecute.enabled:=true; lua_sethook(luavm, linehook, 0, 0); LuaDebugForm:=nil; mscript.OnMouseEnter:=nil; mscript.OnMouseLeave:=nil; if hintwindow<>nil then hintwindow.hide; end; lua_settop(luavm, oldstack); lua_setPrintOutput(oldprintoutput); luacs.Leave; end; end; procedure TfrmLuaEngine.Button1Click(Sender: TObject); begin end; procedure TfrmLuaEngine.dlgReplaceFind(Sender: TObject); var so: TSynSearchOptions; begin so:=[]; if not (frDown in dlgReplace.Options) then so:=so+[ssoBackwards]; if (frEntireScope in dlgReplace.Options) then so:=so+[ssoEntireScope]; if (frMatchCase in dlgReplace.Options) then so:=so+[ssoMatchCase]; if (frPromptOnReplace in dlgReplace.Options) then so:=so+[ssoPrompt]; if (frFindNext in dlgReplace.Options) then so:=so+[ssoFindContinue]; if (frWholeWord in dlgReplace.Options) then so:=so+[ssoWholeWord]; {if mscript.SelAvail then todo: Try to get this to work in all cases so:=so+[ssoSelectedOnly]; } mscript.SearchReplace(dlgReplace.FindText,'',so); end; procedure TfrmLuaEngine.dlgReplaceReplace(Sender: TObject); var so: TSynSearchOptions; begin so:=[]; if not (frDown in dlgReplace.Options) then so:=so+[ssoBackwards]; if (frEntireScope in dlgReplace.Options) then so:=so+[ssoEntireScope]; if (frMatchCase in dlgReplace.Options) then so:=so+[ssoMatchCase]; if (frPromptOnReplace in dlgReplace.Options) then so:=so+[ssoPrompt]; if (frReplace in dlgReplace.Options) then so:=so+[ssoReplace]; if (frReplaceAll in dlgReplace.Options) then so:=so+[ssoReplaceAll]; if (frFindNext in dlgReplace.Options) then so:=so+[ssoFindContinue]; if (frWholeWord in dlgReplace.Options) then so:=so+[ssoWholeWord]; { if mscript.SelAvail and not (ssoFindContinue in so) then so:=so+[ssoSelectedOnly];} mscript.SearchReplace(dlgReplace.FindText,dlgReplace.ReplaceText,so); end; procedure TfrmLuaEngine.FormCreate(Sender: TObject); var x: array of integer; begin synhighlighter:=TSynLuaSyn.Create(self); mscript.Highlighter:=synhighlighter; setlength(x,1); if LoadFormPosition(self, x) then begin panel1.height:=x[0]; if length(x)>1 then begin miResizeOutput.checked:=x[1]=1; miResizeOutput.OnClick(miResizeOutput); end; end; end; procedure TfrmLuaEngine.FormDestroy(Sender: TObject); begin SaveFormPosition(self, [panel1.height, integer(ifthen(miResizeOutput.checked, 1,0))]); end; procedure TfrmLuaEngine.FormShow(Sender: TObject); var i: integer; begin i:=GetFontData(font.handle).Height; if inil then begin for i:=0 to ml.Count-1 do begin if ml[i].ImageIndex in [0,2] then hasbp:=true; end; if hasbp then begin //clear it i:=0; while inil then hintwindow.hide; end; procedure TfrmLuaEngine.mScriptShowHint(Sender: TObject; HintInfo: PHintInfo); begin end; initialization {$I frmluaengineunit.lrs} end.