Setup.ScriptRunner.pas 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. unit Setup.ScriptRunner;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2024 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. Setup.ScriptClasses, Setup.ScriptFunc;
  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. vtWideString:
  134. begin
  135. Param := CreateHeapVariant(FPSExec.FindType2(btWideString));
  136. PPSVariantWString(Param).Data := WideString(Parameters[I].VWideString);
  137. end;
  138. vtUnicodeString:
  139. begin
  140. Param := CreateHeapVariant(FPSExec.FindType2(btUnicodeString));
  141. PPSVariantUString(Param).Data := UnicodeString(Parameters[I].VUnicodeString);
  142. end;
  143. vtInteger:
  144. begin
  145. Param := CreateHeapVariant(FPSExec.FindType2(btS32));
  146. PPSVariantS32(Param).Data := Parameters[I].vInteger;
  147. end;
  148. vtBoolean:
  149. begin
  150. Param := CreateHeapVariant(FPSExec.FindType2(btU8));
  151. PPSVariantU8(Param).Data := Byte(Parameters[I].vBoolean);
  152. end;
  153. vtPointer:
  154. begin
  155. { Pointers are assumed to be pointers to Booleans }
  156. Param := CreateHeapVariant(FPSExec.FindType2(btU8));
  157. PPSVariantU8(Param).Data := Byte(Boolean(Parameters[I].VPointer^));
  158. end;
  159. else
  160. raise Exception.Create('TScriptRunner.SetPSExecParameters: Invalid type');
  161. end;
  162. Params.Add(Param);
  163. end;
  164. end;
  165. procedure TScriptRunner.SetPSExecReturnValue(Params: TPSList; BaseType: TPSBaseType; var Res: PPSVariant);
  166. begin
  167. Res := CreateHeapVariant(FPSExec.FindType2(BaseType));
  168. Params.Add(Res);
  169. end;
  170. {---}
  171. function EncodeDLLFilenameForROPS(const Filename: String): AnsiString;
  172. begin
  173. Result := '';
  174. if Filename <> '' then
  175. Result := AnsiString('<utf8>') + UTF8Encode(Filename);
  176. end;
  177. function NewUnloadDLLProc(Caller: TPSExec; Proc: TPSExternalProcRec; Global, Stack: TPSStack): Boolean;
  178. begin
  179. UnloadDLL(Caller, EncodeDLLFilenameForROPS(Stack.GetString(-1)));
  180. Result := True;
  181. end;
  182. function PSExecOnSpecialProcImport(Sender: TPSExec; p: TPSExternalProcRec; Tag: Pointer): Boolean;
  183. const
  184. SYesNo: array[Boolean] of String = ('No', 'Yes');
  185. var
  186. ScriptRunner: TScriptRunner;
  187. S, DllName, FunctionName: AnsiString;
  188. UnicodeDllName: String;
  189. I: Integer;
  190. ForceDelayLoad: Boolean;
  191. ErrorCode: LongInt;
  192. begin
  193. ScriptRunner := Sender.ID;
  194. ForceDelayLoad := False;
  195. ScriptRunner.Log('-- DLL function import --');
  196. S := p.Decl;
  197. I := Pos(AnsiString('dll:'), S);
  198. if I <> 1 then begin
  199. Result := False;
  200. Exit;
  201. end;
  202. Delete(S, 1, Length('dll:'));
  203. I := Pos(AnsiString(#0), S);
  204. if I = 0 then begin
  205. Result := False;
  206. Exit;
  207. end;
  208. DllName := Copy(S, 1, I-1);
  209. Delete(S, 1, I);
  210. I := Pos(AnsiString(#0), S);
  211. if I = 0 then begin
  212. Result := False;
  213. Exit;
  214. end;
  215. FunctionName := Copy(S, 1, I-1);
  216. UnicodeDllName := UTF8ToString(DllName);
  217. ScriptRunner.LogFmt('Function and DLL name: %s@%s', [FunctionName, UnicodeDllName]);
  218. if Assigned(ScriptRunner.FOnDllImport) then begin
  219. ScriptRunner.FOnDllImport(UnicodeDllName, ForceDelayLoad);
  220. DllName := EncodeDLLFilenameForROPS(UnicodeDllName);
  221. p.Decl := AnsiString('dll:') + DllName + Copy(p.Decl, Pos(AnsiString(#0), p.Decl), MaxInt);
  222. end;
  223. if DllName <> '' then
  224. ScriptRunner.LogFmt('Importing the DLL function. Dest DLL name: %s', [UnicodeDllName])
  225. else
  226. ScriptRunner.Log('Skipping.'); { We're actually still going to call ProcessDllImport but this doesn't matter to the user. }
  227. var DelayLoaded: Boolean;
  228. Result := ProcessDllImportEx2(Sender, p, ForceDelayLoad, DelayLoaded, ErrorCode);
  229. if DllName <> '' then begin
  230. if Result then
  231. ScriptRunner.LogFmt('Successfully imported the DLL function. Delay loaded? %s', [SYesNo[DelayLoaded]])
  232. else
  233. ScriptRunner.LogFmt('Failed to import the DLL function (%d).', [ErrorCode]);
  234. end;
  235. end;
  236. procedure PSExecOnSourceLine(Sender: TPSDebugExec; const Name: AnsiString; Position, Row, Col: Cardinal);
  237. var
  238. ScriptRunner: TScriptRunner;
  239. ContinueStepOver, NeedToResume: Boolean;
  240. begin
  241. ScriptRunner := Sender.ID;
  242. ContinueStepOver := False;
  243. if Sender.DebugMode = dmPaused then begin
  244. if Assigned(ScriptRunner.FOnDebug) then
  245. ScriptRunner.FOnDebug(Position, ContinueStepOver);
  246. NeedToResume := True;
  247. end else begin
  248. { Normally the debugger does not pause when it receives an 'intermediate'
  249. notification. However, it can happen if the user clicks Step Over and
  250. then Pause before the function call being stepped over has returned. }
  251. NeedToResume := False;
  252. if Assigned(ScriptRunner.FOnDebugIntermediate) then
  253. NeedToResume := ScriptRunner.FOnDebugIntermediate(Position, ContinueStepOver);
  254. end;
  255. if NeedToResume then begin
  256. if ContinueStepOver then
  257. Sender.StepOver()
  258. else
  259. Sender.StepInto();
  260. end;
  261. end;
  262. procedure PSExecOnException(Sender: TPSExec; ExError: TPSError; const ExParam: AnsiString; ExObject: TObject; ProcNo, Position: Cardinal);
  263. var
  264. ScriptRunner: TScriptRunner;
  265. begin
  266. ScriptRunner := Sender.ID;
  267. if Assigned(ScriptRunner.FOnException) then
  268. ScriptRunner.FOnException(PSErrorToString(ExError, ExParam), ScriptRunner.FPSExec.TranslatePosition(ProcNo, Position));
  269. { Clear any previous 'step over' state after an exception. Like Delphi,
  270. when F8 is pressed after an exception it should go to the first line of
  271. the nearest 'except' handler, not to the next line of some higher-level
  272. function that the user was stepping over prior to the exception. }
  273. ScriptRunner.FPSExec.StepInto();
  274. end;
  275. {---}
  276. constructor TScriptRunner.Create();
  277. begin
  278. FPSExec := TPSDebugExec.Create();
  279. FPSExec.ID := Self;
  280. FPSExec.AddSpecialProcImport('dll', @PSExecOnSpecialProcImport, nil);
  281. FPSExec.OnSourceLine := PSExecOnSourceLine;
  282. FPSExec.OnException := PSExecOnException;
  283. RegisterDLLRuntimeEx(FPSExec, False, False);
  284. FPSExec.RegisterFunctionName('UNLOADDLL', NewUnloadDLLProc, nil, nil);
  285. FClassImporter := ScriptClassesLibraryRegister_R(FPSExec);
  286. ScriptFuncLibraryRegister_R(FPSExec);
  287. end;
  288. destructor TScriptRunner.Destroy;
  289. begin
  290. FPSExec.Free();
  291. FClassImporter.Free();
  292. end;
  293. procedure TScriptRunner.LoadScript(const CompiledScriptText, CompiledScriptDebugInfo: AnsiString);
  294. begin
  295. if FPSExec.LoadData(CompiledScriptText) then begin
  296. FPSExec.DebugEnabled := CompiledScriptDebugInfo <> '';
  297. if FPSExec.DebugEnabled then
  298. FPSExec.LoadDebugData(CompiledScriptDebugInfo);
  299. FPSExec.StepInto();
  300. end else begin
  301. RaisePSExecException;
  302. { In the case the above for some reason doesn't raise an exception, raise
  303. our own: }
  304. raise Exception.Create('TScriptRunner.LoadScript failed');
  305. end;
  306. end;
  307. function TScriptRunner.GetProcNos(const Name: AnsiString; const CheckNamingAttribute: Boolean; const ProcNos: TPSList): Integer;
  308. var
  309. MainProcNo, ProcNo: Cardinal;
  310. Proc: PIFProcRec;
  311. Attr: TPSRuntimeAttribute;
  312. begin
  313. Result := 0;
  314. { Locate main implementation. Will add later. }
  315. MainProcNo := FPSExec.GetProc(Name);
  316. { Locate other implementations using attributes. }
  317. if CheckNamingAttribute and (FNamingAttribute <> '') then begin
  318. for ProcNo := 0 to FPSExec.GetProcCount-1 do begin
  319. if ProcNo <> MainProcNo then begin
  320. Proc := FPSExec.GetProcNo(ProcNo);
  321. if Proc.Attributes.Count > 0 then begin
  322. Attr := Proc.Attributes.FindAttribute(AnsiString(FNamingAttribute));
  323. if (Attr <> nil) and (Attr.ValueCount = 1) and
  324. (((Attr.Value[0].FType.BaseType = btUnicodeString) and (CompareText(PPSVariantUString(Attr.Value[0]).Data, Name) = 0)) or
  325. ((Attr.Value[0].FType.BaseType = btString) and (CompareText(PPSVariantAString(Attr.Value[0]).Data, Name) = 0))) then begin
  326. if ProcNos <> nil then
  327. ProcNos.Add(Pointer(ProcNo));
  328. Inc(Result);
  329. end;
  330. end;
  331. end;
  332. end;
  333. end;
  334. { Add main implementation. Doing this last so it will be called last always. }
  335. if MainProcNo <> Cardinal(-1) then begin
  336. if ProcNos <> nil then
  337. ProcNos.Add(Pointer(MainProcNo));
  338. Inc(Result);
  339. end;
  340. end;
  341. function TScriptRunner.FunctionExists(const Name: AnsiString; const CheckNamingAttribute: Boolean): Boolean;
  342. begin
  343. Result := GetProcNos(Name, CheckNamingAttribute, nil) <> 0;
  344. end;
  345. procedure WriteBackParameters(const Parameters: array of Const; const Params: TPSList);
  346. var
  347. I: Integer;
  348. begin
  349. { Write back new Boolean values to vtPointer-type parameters }
  350. for I := 0 to High(Parameters) do
  351. if Parameters[I].vType = vtPointer then
  352. Boolean(Parameters[I].VPointer^) := (PPSVariantU8(Params[High(Parameters)-I]).Data = 1);
  353. end;
  354. procedure TScriptRunner.InternalRunProcedure(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute, MustExist: Boolean);
  355. var
  356. ProcNos, Params: TPSList;
  357. I: Integer;
  358. begin
  359. ProcNos := TPSList.Create;
  360. try
  361. if GetProcNos(Name, CheckNamingAttribute, ProcNos) <> 0 then begin
  362. ScriptClassesLibraryUpdateVars(FPSExec);
  363. for I := 0 to ProcNos.Count-1 do begin
  364. Params := TPSList.Create();
  365. try
  366. SetPSExecParameters(Parameters, Params);
  367. FPSExec.RunProc(Params, Cardinal(ProcNos[I]));
  368. WriteBackParameters(Parameters, Params);
  369. RaisePSExecException;
  370. finally
  371. FreePSVariantList(Params);
  372. end;
  373. end;
  374. end else begin
  375. if MustExist then
  376. ShowPSExecError(erCouldNotCallProc);
  377. end;
  378. finally
  379. ProcNos.Free;
  380. end;
  381. end;
  382. procedure TScriptRunner.RunProcedure(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean);
  383. begin
  384. InternalRunProcedure(Name, Parameters, False, MustExist);
  385. end;
  386. procedure TScriptRunner.RunProcedures(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean);
  387. begin
  388. InternalRunProcedure(Name, Parameters, True, MustExist);
  389. end;
  390. function TScriptRunner.InternalRunBooleanFunction(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute: Boolean; const BreakCondition: TBreakCondition; const MustExist, Default: Boolean): Boolean;
  391. var
  392. ProcNos, Params: TPSList;
  393. Res: PPSVariant;
  394. I: Integer;
  395. begin
  396. ProcNos := TPSList.Create;
  397. try
  398. if GetProcNos(Name, CheckNamingAttribute, ProcNos) <> 0 then begin
  399. if not (BreakCondition in [bcNone, bcTrue, bcFalse]) or
  400. ((BreakCondition = bcNone) and (ProcNos.Count > 1)) then
  401. ShowError('Internal error: InternalRunBooleanFunction: invalid BreakCondition');
  402. Result := True; { Silence compiler }
  403. ScriptClassesLibraryUpdateVars(FPSExec);
  404. for I := 0 to ProcNos.Count-1 do begin
  405. Params := TPSList.Create();
  406. try
  407. SetPSExecParameters(Parameters, Params);
  408. SetPSExecReturnValue(Params, btU8, Res);
  409. FPSExec.RunProc(Params, Cardinal(ProcNos[I]));
  410. WriteBackParameters(Parameters, Params);
  411. RaisePSExecException;
  412. Result := PPSVariantU8(Res).Data = 1;
  413. if (Result and (BreakCondition = bcTrue)) or
  414. (not Result and (BreakCondition = bcFalse)) then
  415. Exit;
  416. finally
  417. FreePSVariantList(Params);
  418. end;
  419. end;
  420. end else begin
  421. if MustExist then
  422. ShowPSExecError(erCouldNotCallProc);
  423. Result := Default;
  424. end;
  425. finally
  426. ProcNos.Free;
  427. end;
  428. end;
  429. function TScriptRunner.RunBooleanFunction(const Name: AnsiString; const Parameters: array of Const; const MustExist, Default: Boolean): Boolean;
  430. begin
  431. Result := InternalRunBooleanFunction(Name, Parameters, False, bcNone, MustExist, Default);
  432. end;
  433. function TScriptRunner.RunBooleanFunctions(const Name: AnsiString; const Parameters: array of Const; const BreakCondition: TBreakCondition; const MustExist, Default: Boolean): Boolean;
  434. begin
  435. Result := InternalRunBooleanFunction(Name, Parameters, True, BreakCondition, MustExist, Default);
  436. end;
  437. function TScriptRunner.InternalRunIntegerFunction(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute: Boolean; const BreakCondition: TBreakCondition; const MustExist: Boolean; const Default: Integer): Integer;
  438. var
  439. ProcNos, Params: TPSList;
  440. Res: PPSVariant;
  441. I: Integer;
  442. begin
  443. ProcNos := TPSList.Create;
  444. try
  445. if GetProcNos(Name, CheckNamingAttribute, ProcNos) <> 0 then begin
  446. if not (BreakCondition in [bcNone, bcNonZero]) or
  447. ((BreakCondition = bcNone) and (ProcNos.Count > 1)) then
  448. ShowError('Internal error: InternalRunIntegerFunction: invalid BreakCondition');
  449. Result := 0; { Silence compiler }
  450. ScriptClassesLibraryUpdateVars(FPSExec);
  451. for I := 0 to ProcNos.Count-1 do begin
  452. Params := TPSList.Create();
  453. try
  454. SetPSExecParameters(Parameters, Params);
  455. SetPSExecReturnValue(Params, btS32, Res);
  456. FPSExec.RunProc(Params, Cardinal(ProcNos[I]));
  457. WriteBackParameters(Parameters, Params);
  458. RaisePSExecException;
  459. Result := PPSVariantS32(Res).Data;
  460. if (Result <> 0) and (BreakCondition = bcNonZero) then
  461. Exit;
  462. finally
  463. FreePSVariantList(Params);
  464. end;
  465. end;
  466. end else begin
  467. if MustExist then
  468. ShowPSExecError(erCouldNotCallProc);
  469. Result := Default;
  470. end;
  471. finally
  472. ProcNos.Free;
  473. end;
  474. end;
  475. function TScriptRunner.RunIntegerFunction(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean; const Default: Integer): Integer;
  476. begin
  477. Result := InternalRunIntegerFunction(Name, Parameters, False, bcNone, MustExist, Default);
  478. end;
  479. function TScriptRunner.RunIntegerFunctions(const Name: AnsiString; const Parameters: array of Const; const BreakCondition: TBreakCondition; const MustExist: Boolean; Default: Integer): Integer;
  480. begin
  481. Result := InternalRunIntegerFunction(Name, Parameters, True, BreakCondition, MustExist, Default);
  482. end;
  483. function TScriptRunner.InternalRunStringFunction(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute: Boolean; const BreakCondition: TBreakCondition; const MustExist: Boolean; const Default: String): String;
  484. var
  485. ProcNos, Params: TPSList;
  486. Res: PPSVariant;
  487. I: Integer;
  488. begin
  489. ProcNos := TPSList.Create;
  490. try
  491. if GetProcNos(Name, CheckNamingAttribute, ProcNos) <> 0 then begin
  492. if not (BreakCondition in [bcNone, bcNonEmpty]) or
  493. ((BreakCondition = bcNone) and (ProcNos.Count > 1)) then
  494. ShowError('Internal error: InternalRunStringFunction: invalid BreakCondition');
  495. Result := ''; { Silence compiler }
  496. ScriptClassesLibraryUpdateVars(FPSExec);
  497. for I := 0 to ProcNos.Count-1 do begin
  498. Params := TPSList.Create();
  499. try
  500. SetPSExecParameters(Parameters, Params);
  501. SetPSExecReturnValue(Params, btUnicodeString, Res);
  502. FPSExec.RunProc(Params, Cardinal(ProcNos[I]));
  503. WriteBackParameters(Parameters, Params);
  504. RaisePSExecException;
  505. Result := PPSVariantUString(Res).Data;
  506. if (Result <> '') and (BreakCondition = bcNonEmpty) then
  507. Exit;
  508. finally
  509. FreePSVariantList(Params);
  510. end;
  511. end;
  512. end else begin
  513. if MustExist then
  514. ShowPSExecError(erCouldNotCallProc);
  515. Result := Default;
  516. end;
  517. finally
  518. ProcNos.Free;
  519. end;
  520. end;
  521. function TScriptRunner.RunStringFunction(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean; const Default: String): String;
  522. begin
  523. Result := InternalRunStringFunction(Name, Parameters, False, bcNone, MustExist, Default);
  524. end;
  525. function TScriptRunner.RunStringFunctions(const Name: AnsiString; const Parameters: array of Const; const BreakCondition: TBreakCondition; const MustExist: Boolean; Default: String): String;
  526. begin
  527. Result := InternalRunStringFunction(Name, Parameters, True, BreakCondition, MustExist, Default);
  528. end;
  529. function TScriptRunner.EvaluateUsedVariable(const Param1, Param2, Param3: LongInt; const Param4: AnsiString): String;
  530. function VariantToString(const p: TPSVariantIFC; const ClassProperties: AnsiString): String;
  531. begin
  532. //PSVariantToString isn't Unicode enabled, handle strings ourselves
  533. //doesn't handle more complex types as records, arrays and objects
  534. if p.Dta <> nil then begin
  535. case p.aType.BaseType of
  536. btWideChar: Result := '''' + tbtWideChar(p.Dta^) + '''';
  537. btWideString: Result := '''' + tbtWideString(p.Dta^) + '''';
  538. btUnicodeString: Result := '''' + tbtUnicodeString(p.Dta^) + '''';
  539. else
  540. Result := PSVariantToString(p, ClassProperties);
  541. end;
  542. end else
  543. Result := PSVariantToString(p, ClassProperties);
  544. end;
  545. begin
  546. case TPSVariableType(Param1) of
  547. ivtGlobal:
  548. begin
  549. Result := FPSExec.GlobalVarNames[Param3];
  550. if Param4 <> '' then
  551. Result := Result + '.' + Param4;
  552. Result := Result + ' = ' + VariantToString(NewTPSVariantIFC(FPSExec.GetGlobalVar(Param3), False), Param4);
  553. end;
  554. ivtParam:
  555. begin
  556. if Param2 = LongInt(FPSExec.GetCurrentProcNo) then begin
  557. Result := FPSExec.CurrentProcParams[Param3];
  558. if Param4 <> '' then
  559. Result := Result + '.' + Param4;
  560. Result := Result + ' = ' + VariantToString(NewTPSVariantIFC(FPSExec.GetProcParam(Param3), False), Param4);
  561. end else
  562. Result := '';
  563. end;
  564. ivtVariable:
  565. begin
  566. if Param2 = LongInt(FPSExec.GetCurrentProcNo) then begin
  567. Result := FPSExec.CurrentProcVars[Param3];
  568. if Param4 <> '' then
  569. Result := Result + '.' + Param4;
  570. Result := Result + ' = ' + VariantToString(NewTPSVariantIFC(FPSExec.GetProcVar(Param3), False), Param4);
  571. end else
  572. Result := '';
  573. end;
  574. end;
  575. end;
  576. function TScriptRunner.GetCallStack(var CallStackCount: Cardinal): String;
  577. begin
  578. Result := FPSExec.GetCallStack(CallStackCount);
  579. end;
  580. end.