ScriptRunner.pas 25 KB

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