Setup.ScriptRunner.pas 25 KB

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