ScriptRunner.pas 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. unit ScriptRunner;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2019 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Script runner
  8. }
  9. interface
  10. uses
  11. uPSRuntime, uPSDebugger, uPSUtils;
  12. type
  13. TScriptRunnerOnLog = procedure(const S: String);
  14. TScriptRunnerOnLogFmt = procedure(const S: String; const Args: array of const);
  15. TScriptRunnerOnDllImport = procedure(var DllName: String; var ForceDelayLoad: Boolean);
  16. TScriptRunnerOnDebug = function(const Position: LongInt; var ContinueStepOver: Boolean): Boolean;
  17. TScriptRunnerOnDebugIntermediate = function(const Position: LongInt; var ContinueStepOver: Boolean): Boolean;
  18. TScriptRunnerOnException = procedure(const Exception: AnsiString; const Position: LongInt);
  19. TBreakCondition = (bcNone, bcTrue, bcFalse, bcNonZero, bcNonEmpty);
  20. TScriptRunner = class
  21. private
  22. FNamingAttribute: String;
  23. FPSExec: TPSDebugExec;
  24. FClassImporter: TPSRuntimeClassImporter;
  25. FOnLog: TScriptRunnerOnLog;
  26. FOnLogFmt: TScriptRunnerOnLogFmt;
  27. FOnDllImport: TScriptRunnerOnDllImport;
  28. FOnDebug: TScriptRunnerOnDebug;
  29. FOnDebugIntermediate: TScriptRunnerOnDebugIntermediate;
  30. FOnException: TScriptRunnerOnException;
  31. function GetProcNos(const Name: AnsiString; const CheckNamingAttribute: Boolean; const ProcNos: TPSList): Integer;
  32. procedure InternalRunProcedure(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute, MustExist: Boolean);
  33. function InternalRunBooleanFunction(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute: Boolean; const BreakCondition: TBreakCondition; const MustExist, Default: Boolean): Boolean;
  34. function InternalRunIntegerFunction(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute: Boolean; const BreakCondition: TBreakCondition; const MustExist: Boolean; const Default: Integer): Integer;
  35. function InternalRunStringFunction(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute: Boolean; const BreakCondition: TBreakCondition; const MustExist: Boolean; const Default: String): String;
  36. procedure Log(const S: String);
  37. procedure LogFmt(const S: String; const Args: array of const);
  38. procedure RaisePSExecException;
  39. procedure SetPSExecParameters(const Parameters: array of Const; Params: TPSList);
  40. procedure SetPSExecReturnValue(Params: TPSList; BaseType: TPSBaseType; var Res: PPSVariant);
  41. public
  42. constructor Create;
  43. destructor Destroy; override;
  44. procedure LoadScript(const CompiledScriptText, CompiledScriptDebugInfo: AnsiString);
  45. function FunctionExists(const Name: AnsiString; const CheckNamingAttribute: Boolean): Boolean;
  46. procedure RunProcedure(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean);
  47. procedure RunProcedures(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean);
  48. function RunBooleanFunction(const Name: AnsiString; const Parameters: array of Const; const MustExist, Default: Boolean): Boolean;
  49. function RunBooleanFunctions(const Name: AnsiString; const Parameters: array of Const; const BreakCondition: TBreakCondition; const MustExist, Default: Boolean): Boolean;
  50. function RunIntegerFunction(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean; const Default: Integer): Integer;
  51. function RunIntegerFunctions(const Name: AnsiString; const Parameters: array of Const; const BreakCondition: TBreakCondition; const MustExist: Boolean; Default: Integer): Integer;
  52. function RunStringFunction(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean; const Default: String): String;
  53. function RunStringFunctions(const Name: AnsiString; const Parameters: array of Const; const BreakCondition: TBreakCondition; const MustExist: Boolean; Default: String): String;
  54. function EvaluateUsedVariable(const Param1, Param2, Param3: LongInt; const Param4: AnsiString): String;
  55. function GetCallStack(var CallStackCount: Cardinal): String;
  56. property NamingAttribute: String write FNamingAttribute;
  57. property OnLog: TScriptRunnerOnLog read FOnLog write FOnLog;
  58. property OnLogFmt: TScriptRunnerOnLogFmt read FOnLogFmt write FOnLogFmt;
  59. property OnDllImport: TScriptRunnerOnDllImport read FOnDllImport write FOnDllImport;
  60. property OnDebug: TScriptRunnerOnDebug read FOnDebug write FOnDebug;
  61. property OnDebugIntermediate: TScriptRunnerOnDebugIntermediate read FOnDebugIntermediate write FOnDebugIntermediate;
  62. property OnException: TScriptRunnerOnException read FOnException write FOnException;
  63. end;
  64. implementation
  65. uses
  66. Windows,
  67. Forms, SysUtils,
  68. uPSR_dll,
  69. ScriptClasses_R, ScriptFunc_R;
  70. {---}
  71. { Note: Originally this unit used String() casts to avoid "Implicit string
  72. cast" warnings on Delphi 2009, but the casts were found to cause non-Unicode
  73. Setup to crash during tooltip variable evaluation due to some kind of code
  74. generation bug in Delphi 2. Removed all casts, and added the following to
  75. simply disable the warning. }
  76. {$IFDEF UNICODE}
  77. {$WARN IMPLICIT_STRING_CAST OFF}
  78. {$ENDIF}
  79. procedure TScriptRunner.Log(const S: String);
  80. begin
  81. if Assigned(FOnLog) then
  82. FOnLog(S);
  83. end;
  84. procedure TScriptRunner.LogFmt(const S: String; const Args: array of const);
  85. begin
  86. if Assigned(FOnLogFmt) then
  87. FOnLogFmt(S, Args);
  88. end;
  89. procedure ShowError(const Error: String);
  90. begin
  91. raise Exception.Create(Error);
  92. end;
  93. procedure ShowPSExecError(const Error: TPSError);
  94. begin
  95. ShowError('Script error: ' + PSErrorToString(Error, ''));
  96. end;
  97. procedure TScriptRunner.RaisePSExecException;
  98. var
  99. E: TObject;
  100. begin
  101. try
  102. FPSExec.RaiseCurrentException;
  103. except
  104. { Note: Don't use 'on E: Exception do' since that will also match
  105. 'Exception' objects raised from other modules (which we mustn't modify) }
  106. E := ExceptObject;
  107. if E is Exception then begin
  108. Exception(E).Message := Format('Runtime error (at %d:%d):'#13#10#13#10,
  109. [FPSExec.ExceptionProcNo, FPSExec.ExceptionPos]) + Exception(E).Message;
  110. raise;
  111. end
  112. else begin
  113. { If we don't see it as an Exception, it was likely raised by another
  114. module }
  115. raise Exception.CreateFmt('Runtime error (at %d:%d):'#13#10#13#10 +
  116. 'Exception "%s" at address %p',
  117. [FPSExec.ExceptionProcNo, FPSExec.ExceptionPos, E.ClassName, ExceptAddr]);
  118. end;
  119. end;
  120. end;
  121. procedure TScriptRunner.SetPSExecParameters(const Parameters: array of Const; Params: TPSList);
  122. var
  123. Param: PPSVariant;
  124. I: Integer;
  125. begin
  126. for I := High(Parameters) downto Low(Parameters) do begin
  127. case Parameters[I].vType of
  128. vtAnsiString:
  129. begin
  130. Param := CreateHeapVariant(FPSExec.FindType2(btString));
  131. PPSVariantAString(Param).Data := AnsiString(Parameters[I].vAnsiString);
  132. end;
  133. {$IFDEF UNICODE}
  134. vtWideString:
  135. begin
  136. Param := CreateHeapVariant(FPSExec.FindType2(btWideString));
  137. PPSVariantWString(Param).Data := WideString(Parameters[I].VWideString);
  138. end;
  139. vtUnicodeString:
  140. begin
  141. Param := CreateHeapVariant(FPSExec.FindType2(btUnicodeString));
  142. PPSVariantUString(Param).Data := UnicodeString(Parameters[I].VUnicodeString);
  143. end;
  144. {$ENDIF}
  145. vtInteger:
  146. begin
  147. Param := CreateHeapVariant(FPSExec.FindType2(btS32));
  148. PPSVariantS32(Param).Data := Parameters[I].vInteger;
  149. end;
  150. vtBoolean:
  151. begin
  152. Param := CreateHeapVariant(FPSExec.FindType2(btU8));
  153. PPSVariantU8(Param).Data := Byte(Parameters[I].vBoolean);
  154. end;
  155. vtPointer:
  156. begin
  157. { Pointers are assumed to be pointers to Booleans }
  158. Param := CreateHeapVariant(FPSExec.FindType2(btU8));
  159. PPSVariantU8(Param).Data := Byte(Boolean(Parameters[I].VPointer^));
  160. end;
  161. else
  162. raise Exception.Create('TScriptRunner.SetPSExecParameters: Invalid type');
  163. end;
  164. Params.Add(Param);
  165. end;
  166. end;
  167. procedure TScriptRunner.SetPSExecReturnValue(Params: TPSList; BaseType: TPSBaseType; var Res: PPSVariant);
  168. begin
  169. Res := CreateHeapVariant(FPSExec.FindType2(BaseType));
  170. Params.Add(Res);
  171. end;
  172. {---}
  173. {$IFDEF UNICODE}
  174. function EncodeDLLFilenameForROPS(const Filename: String): AnsiString;
  175. begin
  176. Result := '';
  177. if Filename <> '' then
  178. Result := AnsiString('<utf8>') + UTF8Encode(Filename);
  179. end;
  180. function NewUnloadDLLProc(Caller: TPSExec; Proc: TPSExternalProcRec; Global, Stack: TPSStack): Boolean;
  181. begin
  182. UnloadDLL(Caller, EncodeDLLFilenameForROPS(Stack.GetString(-1)));
  183. Result := True;
  184. end;
  185. {$ENDIF}
  186. function PSExecOnSpecialProcImport(Sender: TPSExec; p: TPSExternalProcRec; Tag: Pointer): Boolean;
  187. const
  188. SYesNo: array[Boolean] of String = ('No', 'Yes');
  189. var
  190. ScriptRunner: TScriptRunner;
  191. S, DllName, FunctionName: AnsiString;
  192. {$IFDEF UNICODE}
  193. UnicodeDllName: String;
  194. {$ENDIF}
  195. I: Integer;
  196. ForceDelayLoad, DelayLoad: Boolean;
  197. ErrorCode: LongInt;
  198. begin
  199. ScriptRunner := Sender.ID;
  200. ForceDelayLoad := False;
  201. ScriptRunner.Log('-- DLL function import --');
  202. S := p.Decl;
  203. I := Pos(AnsiString('dll:'), S);
  204. if I <> 1 then begin
  205. Result := False;
  206. Exit;
  207. end;
  208. Delete(S, 1, Length('dll:'));
  209. I := Pos(AnsiString(#0), S);
  210. if I = 0 then begin
  211. Result := False;
  212. Exit;
  213. end;
  214. DllName := Copy(S, 1, I-1);
  215. Delete(S, 1, I);
  216. I := Pos(AnsiString(#0), S);
  217. if I = 0 then begin
  218. Result := False;
  219. Exit;
  220. end;
  221. FunctionName := Copy(S, 1, I-1);
  222. ScriptRunner.LogFmt('Function name: %s', [FunctionName]);
  223. {$IFDEF UNICODE}
  224. UnicodeDllName := UTF8ToString(DllName);
  225. ScriptRunner.LogFmt('DLL name: %s', [UnicodeDllname]);
  226. {$ELSE}
  227. ScriptRunner.LogFmt('DLL name: %s', [DllName]);
  228. {$ENDIF}
  229. if Assigned(ScriptRunner.FOnDllImport) then begin
  230. {$IFDEF UNICODE}
  231. ScriptRunner.FOnDllImport(UnicodeDllName, ForceDelayLoad);
  232. DllName := EncodeDLLFilenameForROPS(UnicodeDllName);
  233. {$ELSE}
  234. ScriptRunner.FOnDllImport(DllName, ForceDelayLoad);
  235. {$ENDIF}
  236. p.Decl := AnsiString('dll:') + DllName + Copy(p.Decl, Pos(AnsiString(#0), p.Decl), MaxInt);
  237. end;
  238. if DllName <> '' then begin
  239. {$IFDEF UNICODE}
  240. ScriptRunner.LogFmt('Dest DLL name: %s', [UnicodeDllName]);
  241. {$ELSE}
  242. ScriptRunner.LogFmt('Dest DLL name: %s', [DllName]);
  243. {$ENDIF}
  244. ScriptRunner.Log('Importing the DLL function.');
  245. end else
  246. ScriptRunner.Log('Skipping.'); { We're actually still going to call ProcessDllImport but this doesn't matter to the user. }
  247. Result := ProcessDllImportEx2(Sender, p, ForceDelayLoad, DelayLoad, ErrorCode);
  248. if DllName <> '' then begin
  249. if Result then
  250. ScriptRunner.LogFmt('Successfully imported the DLL function. Delay loaded? %s', [SYesNo[DelayLoad]])
  251. else
  252. ScriptRunner.LogFmt('Failed to import the DLL function (%d).', [ErrorCode]);
  253. end;
  254. end;
  255. procedure PSExecOnSourceLine(Sender: TPSDebugExec; const Name: AnsiString; Position, Row, Col: Cardinal);
  256. var
  257. ScriptRunner: TScriptRunner;
  258. ContinueStepOver, NeedToResume: Boolean;
  259. begin
  260. ScriptRunner := Sender.ID;
  261. ContinueStepOver := False;
  262. if Sender.DebugMode = dmPaused then begin
  263. if Assigned(ScriptRunner.FOnDebug) then
  264. ScriptRunner.FOnDebug(Position, ContinueStepOver);
  265. NeedToResume := True;
  266. end else begin
  267. { Normally the debugger does not pause when it receives an 'intermediate'
  268. notification. However, it can happen if the user clicks Step Over and
  269. then Pause before the function call being stepped over has returned. }
  270. NeedToResume := False;
  271. if Assigned(ScriptRunner.FOnDebugIntermediate) then
  272. NeedToResume := ScriptRunner.FOnDebugIntermediate(Position, ContinueStepOver);
  273. end;
  274. if NeedToResume then begin
  275. if ContinueStepOver then
  276. Sender.StepOver()
  277. else
  278. Sender.StepInto();
  279. end;
  280. end;
  281. procedure PSExecOnException(Sender: TPSExec; ExError: TPSError; const ExParam: AnsiString; ExObject: TObject; ProcNo, Position: Cardinal);
  282. var
  283. ScriptRunner: TScriptRunner;
  284. begin
  285. ScriptRunner := Sender.ID;
  286. if Assigned(ScriptRunner.FOnException) then
  287. ScriptRunner.FOnException(PSErrorToString(ExError, ExParam), ScriptRunner.FPSExec.TranslatePosition(ProcNo, Position));
  288. { Clear any previous 'step over' state after an exception. Like Delphi,
  289. when F8 is pressed after an exception it should go to the first line of
  290. the nearest 'except' handler, not to the next line of some higher-level
  291. function that the user was stepping over prior to the exception. }
  292. ScriptRunner.FPSExec.StepInto();
  293. end;
  294. {---}
  295. constructor TScriptRunner.Create();
  296. begin
  297. FPSExec := TPSDebugExec.Create();
  298. FPSExec.ID := Self;
  299. FPSExec.AddSpecialProcImport('dll', @PSExecOnSpecialProcImport, nil);
  300. FPSExec.OnSourceLine := PSExecOnSourceLine;
  301. FPSExec.OnException := PSExecOnException;
  302. {$IFNDEF UNICODE}
  303. RegisterDLLRuntimeEx(FPSExec, False);
  304. {$ELSE}
  305. RegisterDLLRuntimeEx(FPSExec, False, False);
  306. FPSExec.RegisterFunctionName('UNLOADDLL', NewUnloadDLLProc, nil, nil);
  307. {$ENDIF}
  308. FClassImporter := ScriptClassesLibraryRegister_R(FPSExec);
  309. ScriptFuncLibraryRegister_R(FPSExec);
  310. end;
  311. destructor TScriptRunner.Destroy;
  312. begin
  313. FPSExec.Free();
  314. FClassImporter.Free();
  315. end;
  316. procedure TScriptRunner.LoadScript(const CompiledScriptText, CompiledScriptDebugInfo: AnsiString);
  317. begin
  318. if FPSExec.LoadData(CompiledScriptText) then begin
  319. FPSExec.DebugEnabled := CompiledScriptDebugInfo <> '';
  320. if FPSExec.DebugEnabled then
  321. FPSExec.LoadDebugData(CompiledScriptDebugInfo);
  322. FPSExec.StepInto();
  323. end else begin
  324. RaisePSExecException;
  325. { In the case the above for some reason doesn't raise an exception, raise
  326. our own: }
  327. raise Exception.Create('TScriptRunner.LoadScript failed');
  328. end;
  329. end;
  330. function TScriptRunner.GetProcNos(const Name: AnsiString; const CheckNamingAttribute: Boolean; const ProcNos: TPSList): Integer;
  331. var
  332. MainProcNo, ProcNo: Cardinal;
  333. Proc: PIFProcRec;
  334. Attr: TPSRuntimeAttribute;
  335. begin
  336. Result := 0;
  337. { Locate main implementation. Will add later. }
  338. MainProcNo := FPSExec.GetProc(Name);
  339. { Locate other implementations using attributes. }
  340. if CheckNamingAttribute and (FNamingAttribute <> '') then begin
  341. for ProcNo := 0 to FPSExec.GetProcCount-1 do begin
  342. if ProcNo <> MainProcNo then begin
  343. Proc := FPSExec.GetProcNo(ProcNo);
  344. if Proc.Attributes.Count > 0 then begin
  345. Attr := Proc.Attributes.FindAttribute(AnsiString(FNamingAttribute));
  346. if (Attr <> nil) and (Attr.ValueCount = 1) and
  347. ({$IFDEF UNICODE} ((Attr.Value[0].FType.BaseType = btUnicodeString) and (CompareText(PPSVariantUString(Attr.Value[0]).Data, Name) = 0)) or {$ENDIF}
  348. ((Attr.Value[0].FType.BaseType = btString) and (CompareText(PPSVariantAString(Attr.Value[0]).Data, Name) = 0))) then begin
  349. if ProcNos <> nil then
  350. ProcNos.Add(Pointer(ProcNo));
  351. Inc(Result);
  352. end;
  353. end;
  354. end;
  355. end;
  356. end;
  357. { Add main implementation. Doing this last so it will be called last always. }
  358. if MainProcNo <> Cardinal(-1) then begin
  359. if ProcNos <> nil then
  360. ProcNos.Add(Pointer(MainProcNo));
  361. Inc(Result);
  362. end;
  363. end;
  364. function TScriptRunner.FunctionExists(const Name: AnsiString; const CheckNamingAttribute: Boolean): Boolean;
  365. begin
  366. Result := GetProcNos(Name, CheckNamingAttribute, nil) <> 0;
  367. end;
  368. procedure WriteBackParameters(const Parameters: array of Const; const Params: TPSList);
  369. var
  370. I: Integer;
  371. begin
  372. { Write back new Boolean values to vtPointer-type parameters }
  373. for I := 0 to High(Parameters) do
  374. if Parameters[I].vType = vtPointer then
  375. Boolean(Parameters[I].VPointer^) := (PPSVariantU8(Params[High(Parameters)-I]).Data = 1);
  376. end;
  377. procedure TScriptRunner.InternalRunProcedure(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute, MustExist: Boolean);
  378. var
  379. ProcNos, Params: TPSList;
  380. I: Integer;
  381. begin
  382. ProcNos := TPSList.Create;
  383. try
  384. if GetProcNos(Name, CheckNamingAttribute, ProcNos) <> 0 then begin
  385. ScriptClassesLibraryUpdateVars(FPSExec);
  386. for I := 0 to ProcNos.Count-1 do begin
  387. Params := TPSList.Create();
  388. try
  389. SetPSExecParameters(Parameters, Params);
  390. FPSExec.RunProc(Params, Cardinal(ProcNos[I]));
  391. WriteBackParameters(Parameters, Params);
  392. RaisePSExecException;
  393. finally
  394. FreePSVariantList(Params);
  395. end;
  396. end;
  397. end else begin
  398. if MustExist then
  399. ShowPSExecError(erCouldNotCallProc);
  400. end;
  401. finally
  402. ProcNos.Free;
  403. end;
  404. end;
  405. procedure TScriptRunner.RunProcedure(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean);
  406. begin
  407. InternalRunProcedure(Name, Parameters, False, MustExist);
  408. end;
  409. procedure TScriptRunner.RunProcedures(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean);
  410. begin
  411. InternalRunProcedure(Name, Parameters, True, MustExist);
  412. end;
  413. function TScriptRunner.InternalRunBooleanFunction(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute: Boolean; const BreakCondition: TBreakCondition; const MustExist, Default: Boolean): Boolean;
  414. var
  415. ProcNos, Params: TPSList;
  416. Res: PPSVariant;
  417. I: Integer;
  418. begin
  419. ProcNos := TPSList.Create;
  420. try
  421. if GetProcNos(Name, CheckNamingAttribute, ProcNos) <> 0 then begin
  422. if not (BreakCondition in [bcNone, bcTrue, bcFalse]) or
  423. ((BreakCondition = bcNone) and (ProcNos.Count > 1)) then
  424. ShowError('Internal error: InternalRunBooleanFunction: invalid BreakCondition');
  425. Result := True; { Silence compiler }
  426. ScriptClassesLibraryUpdateVars(FPSExec);
  427. for I := 0 to ProcNos.Count-1 do begin
  428. Params := TPSList.Create();
  429. try
  430. SetPSExecParameters(Parameters, Params);
  431. SetPSExecReturnValue(Params, btU8, Res);
  432. FPSExec.RunProc(Params, Cardinal(ProcNos[I]));
  433. WriteBackParameters(Parameters, Params);
  434. RaisePSExecException;
  435. Result := PPSVariantU8(Res).Data = 1;
  436. if (Result and (BreakCondition = bcTrue)) or
  437. (not Result and (BreakCondition = bcFalse)) then
  438. Exit;
  439. finally
  440. FreePSVariantList(Params);
  441. end;
  442. end;
  443. end else begin
  444. if MustExist then
  445. ShowPSExecError(erCouldNotCallProc);
  446. Result := Default;
  447. end;
  448. finally
  449. ProcNos.Free;
  450. end;
  451. end;
  452. function TScriptRunner.RunBooleanFunction(const Name: AnsiString; const Parameters: array of Const; const MustExist, Default: Boolean): Boolean;
  453. begin
  454. Result := InternalRunBooleanFunction(Name, Parameters, False, bcNone, MustExist, Default);
  455. end;
  456. function TScriptRunner.RunBooleanFunctions(const Name: AnsiString; const Parameters: array of Const; const BreakCondition: TBreakCondition; const MustExist, Default: Boolean): Boolean;
  457. begin
  458. Result := InternalRunBooleanFunction(Name, Parameters, True, BreakCondition, MustExist, Default);
  459. end;
  460. function TScriptRunner.InternalRunIntegerFunction(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute: Boolean; const BreakCondition: TBreakCondition; const MustExist: Boolean; const Default: Integer): Integer;
  461. var
  462. ProcNos, Params: TPSList;
  463. Res: PPSVariant;
  464. I: Integer;
  465. begin
  466. ProcNos := TPSList.Create;
  467. try
  468. if GetProcNos(Name, CheckNamingAttribute, ProcNos) <> 0 then begin
  469. if not (BreakCondition in [bcNone, bcNonZero]) or
  470. ((BreakCondition = bcNone) and (ProcNos.Count > 1)) then
  471. ShowError('Internal error: InternalRunIntegerFunction: invalid BreakCondition');
  472. Result := 0; { Silence compiler }
  473. ScriptClassesLibraryUpdateVars(FPSExec);
  474. for I := 0 to ProcNos.Count-1 do begin
  475. Params := TPSList.Create();
  476. try
  477. SetPSExecParameters(Parameters, Params);
  478. SetPSExecReturnValue(Params, btS32, Res);
  479. FPSExec.RunProc(Params, Cardinal(ProcNos[I]));
  480. WriteBackParameters(Parameters, Params);
  481. RaisePSExecException;
  482. Result := PPSVariantS32(Res).Data;
  483. if (Result <> 0) and (BreakCondition = bcNonZero) then
  484. Exit;
  485. finally
  486. FreePSVariantList(Params);
  487. end;
  488. end;
  489. end else begin
  490. if MustExist then
  491. ShowPSExecError(erCouldNotCallProc);
  492. Result := Default;
  493. end;
  494. finally
  495. ProcNos.Free;
  496. end;
  497. end;
  498. function TScriptRunner.RunIntegerFunction(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean; const Default: Integer): Integer;
  499. begin
  500. Result := InternalRunIntegerFunction(Name, Parameters, False, bcNone, MustExist, Default);
  501. end;
  502. function TScriptRunner.RunIntegerFunctions(const Name: AnsiString; const Parameters: array of Const; const BreakCondition: TBreakCondition; const MustExist: Boolean; Default: Integer): Integer;
  503. begin
  504. Result := InternalRunIntegerFunction(Name, Parameters, True, BreakCondition, MustExist, Default);
  505. end;
  506. function TScriptRunner.InternalRunStringFunction(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute: Boolean; const BreakCondition: TBreakCondition; const MustExist: Boolean; const Default: String): String;
  507. var
  508. ProcNos, Params: TPSList;
  509. Res: PPSVariant;
  510. I: Integer;
  511. begin
  512. ProcNos := TPSList.Create;
  513. try
  514. if GetProcNos(Name, CheckNamingAttribute, ProcNos) <> 0 then begin
  515. if not (BreakCondition in [bcNone, bcNonEmpty]) or
  516. ((BreakCondition = bcNone) and (ProcNos.Count > 1)) then
  517. ShowError('Internal error: InternalRunStringFunction: invalid BreakCondition');
  518. Result := ''; { Silence compiler }
  519. ScriptClassesLibraryUpdateVars(FPSExec);
  520. for I := 0 to ProcNos.Count-1 do begin
  521. Params := TPSList.Create();
  522. try
  523. SetPSExecParameters(Parameters, Params);
  524. {$IFDEF UNICODE}
  525. SetPSExecReturnValue(Params, btUnicodeString, Res);
  526. {$ELSE}
  527. SetPSExecReturnValue(Params, btString, Res);
  528. {$ENDIF}
  529. FPSExec.RunProc(Params, Cardinal(ProcNos[I]));
  530. WriteBackParameters(Parameters, Params);
  531. RaisePSExecException;
  532. {$IFDEF UNICODE}
  533. Result := PPSVariantUString(Res).Data;
  534. {$ELSE}
  535. Result := PPSVariantAString(Res).Data;
  536. {$ENDIF}
  537. if (Result <> '') and (BreakCondition = bcNonEmpty) then
  538. Exit;
  539. finally
  540. FreePSVariantList(Params);
  541. end;
  542. end;
  543. end else begin
  544. if MustExist then
  545. ShowPSExecError(erCouldNotCallProc);
  546. Result := Default;
  547. end;
  548. finally
  549. ProcNos.Free;
  550. end;
  551. end;
  552. function TScriptRunner.RunStringFunction(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean; const Default: String): String;
  553. begin
  554. Result := InternalRunStringFunction(Name, Parameters, False, bcNone, MustExist, Default);
  555. end;
  556. function TScriptRunner.RunStringFunctions(const Name: AnsiString; const Parameters: array of Const; const BreakCondition: TBreakCondition; const MustExist: Boolean; Default: String): String;
  557. begin
  558. Result := InternalRunStringFunction(Name, Parameters, True, BreakCondition, MustExist, Default);
  559. end;
  560. function TScriptRunner.EvaluateUsedVariable(const Param1, Param2, Param3: LongInt; const Param4: AnsiString): String;
  561. function VariantToString(const p: TPSVariantIFC; const ClassProperties: AnsiString): String;
  562. begin
  563. {$IFDEF UNICODE}
  564. //PSVariantToString isn't Unicode enabled, handle strings ourselves
  565. //doesn't handle more complex types as records, arrays and objects
  566. if p.Dta <> nil then begin
  567. case p.aType.BaseType of
  568. btWideChar: Result := '''' + tbtWideChar(p.Dta^) + '''';
  569. btWideString: Result := '''' + tbtWideString(p.Dta^) + '''';
  570. btUnicodeString: Result := '''' + tbtUnicodeString(p.Dta^) + '''';
  571. else
  572. Result := PSVariantToString(p, ClassProperties);
  573. end;
  574. end else
  575. {$ENDIF}
  576. Result := PSVariantToString(p, ClassProperties);
  577. end;
  578. begin
  579. case TPSVariableType(Param1) of
  580. ivtGlobal:
  581. begin
  582. Result := FPSExec.GlobalVarNames[Param3];
  583. if Param4 <> '' then
  584. Result := Result + '.' + Param4;
  585. Result := Result + ' = ' + VariantToString(NewTPSVariantIFC(FPSExec.GetGlobalVar(Param3), False), Param4);
  586. end;
  587. ivtParam:
  588. begin
  589. if Param2 = LongInt(FPSExec.GetCurrentProcNo) then begin
  590. Result := FPSExec.CurrentProcParams[Param3];
  591. if Param4 <> '' then
  592. Result := Result + '.' + Param4;
  593. Result := Result + ' = ' + VariantToString(NewTPSVariantIFC(FPSExec.GetProcParam(Param3), False), Param4);
  594. end else
  595. Result := '';
  596. end;
  597. ivtVariable:
  598. begin
  599. if Param2 = LongInt(FPSExec.GetCurrentProcNo) then begin
  600. Result := FPSExec.CurrentProcVars[Param3];
  601. if Param4 <> '' then
  602. Result := Result + '.' + Param4;
  603. Result := Result + ' = ' + VariantToString(NewTPSVariantIFC(FPSExec.GetProcVar(Param3), False), Param4);
  604. end else
  605. Result := '';
  606. end;
  607. end;
  608. end;
  609. function TScriptRunner.GetCallStack(var CallStackCount: Cardinal): String;
  610. begin
  611. Result := FPSExec.GetCallStack(CallStackCount);
  612. end;
  613. end.