Browse Source

Add 'event attributes' support which allows you to have more than 1 event function of same type, for example two InitializeSetup's. Intended to be used by included files to avoid conflicts with the main script or other included files.

Todo:
-Compile the ROPS changes needed for this
-Final review and testing
-Doc
Martijn Laan 7 years ago
parent
commit
7de8caed4d

+ 11 - 0
Examples/CodeExample1.iss

@@ -33,6 +33,17 @@ begin
     MsgBox('InitializeSetup:' #13#13 'Ok, bye bye.', mbInformation, MB_OK);
 end;
 
+procedure InitializeWizard;
+begin
+  Log('InitializeWizard called');
+end;
+
+<event('InitializeWizard')>
+procedure InitializeWizard2;
+begin
+  Log('InitializeWizard2 called');
+end;
+
 procedure DeinitializeSetup();
 var
   FileName: String;

+ 36 - 33
Projects/Compile.pas

@@ -1763,6 +1763,12 @@ begin
   CodeDebugInfo := TMemoryStream.Create;
   CodeText := TStringList.Create;
   CodeCompiler := TScriptCompiler.Create;
+  CodeCompiler.NamingAttribute := 'Event';
+  CodeCompiler.OnLineToLineInfo := CodeCompilerOnLineToLineInfo;
+  CodeCompiler.OnUsedLine := CodeCompilerOnUsedLine;
+  CodeCompiler.OnUsedVariable := CodeCompilerOnUsedVariable;
+  CodeCompiler.OnError := CodeCompilerOnError;
+  CodeCompiler.OnWarning := CodeCompilerOnWarning;
 end;
 
 destructor TSetupCompiler.Destroy;
@@ -2806,7 +2812,7 @@ function TSetupCompiler.CheckConst(const S: String; const MinVersion: TSetupVers
     ScriptFunc := Z;
     if ConvertConstPercentStr(ScriptFunc) and ConvertConstPercentStr(Param) then begin
       CheckConst(Param, MinVersion, AllowedConsts);
-      CodeCompiler.AddExport(ScriptFunc, 'String @String', True, ParseFileName, LineNumber);
+      CodeCompiler.AddExport(ScriptFunc, 'String @String', False, True, ParseFileName, LineNumber);
       Result := True;
       Exit;
     end;
@@ -3025,7 +3031,7 @@ begin
       raise Exception.Create('Internal Error: unknown parameter type');
   end;
 
-  CodeCompiler.AddExport(Name, Decl, True, ParseFileName, LineNumber);
+  CodeCompiler.AddExport(Name, Decl, False, True, ParseFileName, LineNumber);
 
   Result := True; { Result doesn't matter }
 end;
@@ -7475,42 +7481,39 @@ var
   CompiledCodeDebugInfo: AnsiString;
 begin
   { Compile CodeText }
-
-  CodeCompiler.OnLineToLineInfo := CodeCompilerOnLineToLineInfo;
-  CodeCompiler.OnUsedLine := CodeCompilerOnUsedLine;
-  CodeCompiler.OnUsedVariable := CodeCompilerOnUsedVariable;
-  CodeCompiler.OnError := CodeCompilerOnError;
-  CodeCompiler.OnWarning := CodeCompilerOnWarning;
-
   if (CodeText.Count > 0) or (CodeCompiler.ExportCount > 0) then begin
     if CodeText.Count > 0 then
       AddStatus(SCompilerStatusCompilingCode);
 
     //don't forget highlighter!
-    CodeCompiler.AddExport('InitializeSetup', 'Boolean', False, '', 0);
-    CodeCompiler.AddExport('DeinitializeSetup', '0', False, '', 0);
-    CodeCompiler.AddExport('CurStepChanged', '0 @TSetupStep', False, '', 0);
-    CodeCompiler.AddExport('NextButtonClick', 'Boolean @LongInt', False, '', 0);
-    CodeCompiler.AddExport('BackButtonClick', 'Boolean @LongInt', False, '', 0);
-    CodeCompiler.AddExport('CancelButtonClick', '0 @LongInt !Boolean !Boolean', False, '', 0);
-    CodeCompiler.AddExport('ShouldSkipPage', 'Boolean @LongInt', False, '', 0);
-    CodeCompiler.AddExport('CurPageChanged', '0 @LongInt', False, '', 0);
-    CodeCompiler.AddExport('CheckPassword', 'Boolean @String', False, '', 0);
-    CodeCompiler.AddExport('NeedRestart', 'Boolean', False, '', 0);
-    CodeCompiler.AddExport('UpdateReadyMemo', 'String @String @String @String @String @String @String @String @String', False, '', 0);
-    CodeCompiler.AddExport('RegisterPreviousData', '0 @LongInt', False, '', 0);
-    CodeCompiler.AddExport('CheckSerial', 'Boolean @String', False, '', 0);
-    CodeCompiler.AddExport('InitializeWizard', '0', False, '', 0);
-    CodeCompiler.AddExport('GetCustomSetupExitCode', 'LongInt', False, '', 0);
-    CodeCompiler.AddExport('PrepareToInstall', 'String !Boolean', False, '', 0);
-    CodeCompiler.AddExport('RegisterExtraCloseApplicationsResources', '0', False, '', 0);
-    CodeCompiler.AddExport('CurInstallProgressChanged', '0 @LongInt @LongInt', False, '', 0);
-
-    CodeCompiler.AddExport('InitializeUninstall', 'Boolean', False, '', 0);
-    CodeCompiler.AddExport('DeinitializeUninstall', '0', False, '', 0);
-    CodeCompiler.AddExport('CurUninstallStepChanged', '0 @TUninstallStep', False, '', 0);
-    CodeCompiler.AddExport('UninstallNeedRestart', 'Boolean', False, '', 0);
-    CodeCompiler.AddExport('InitializeUninstallProgressForm', '0', False, '', 0);
+    //setup + allownamingattribute (=all procedures and boolean functions)
+    CodeCompiler.AddExport('InitializeSetup', 'Boolean', True, False, '', 0);
+    CodeCompiler.AddExport('DeinitializeSetup', '0', True, False, '', 0);
+    CodeCompiler.AddExport('CurStepChanged', '0 @TSetupStep', True, False, '', 0);
+    CodeCompiler.AddExport('NextButtonClick', 'Boolean @LongInt', True, False, '', 0);
+    CodeCompiler.AddExport('BackButtonClick', 'Boolean @LongInt', True, False, '', 0);
+    CodeCompiler.AddExport('CancelButtonClick', '0 @LongInt !Boolean !Boolean', True, False, '', 0);
+    CodeCompiler.AddExport('ShouldSkipPage', 'Boolean @LongInt', True, False, '', 0);
+    CodeCompiler.AddExport('CurPageChanged', '0 @LongInt', True, False, '', 0);
+    CodeCompiler.AddExport('CheckPassword', 'Boolean @String', True, False, '', 0);
+    CodeCompiler.AddExport('NeedRestart', 'Boolean', True, False, '', 0);
+    CodeCompiler.AddExport('RegisterPreviousData', '0 @LongInt', True, False, '', 0);
+    CodeCompiler.AddExport('CheckSerial', 'Boolean @String', True, False, '', 0);
+    CodeCompiler.AddExport('InitializeWizard', '0', True, False, '', 0);
+    CodeCompiler.AddExport('RegisterExtraCloseApplicationsResources', '0', True, False, '', 0);
+    CodeCompiler.AddExport('CurInstallProgressChanged', '0 @LongInt @LongInt', True, False, '', 0);
+
+    //setup + !allownamingattribute (=non boolean functions)
+    CodeCompiler.AddExport('UpdateReadyMemo', 'String @String @String @String @String @String @String @String @String', False, False, '', 0);
+    CodeCompiler.AddExport('GetCustomSetupExitCode', 'LongInt', False, False, '', 0);
+    CodeCompiler.AddExport('PrepareToInstall', 'String !Boolean', False, False, '', 0);
+
+    //uninstall + allownamingattribute (=all procedures and boolean functions)
+    CodeCompiler.AddExport('InitializeUninstall', 'Boolean', True, False, '', 0);
+    CodeCompiler.AddExport('DeinitializeUninstall', '0', True, False, '', 0);
+    CodeCompiler.AddExport('CurUninstallStepChanged', '0 @TUninstallStep', True, False, '', 0);
+    CodeCompiler.AddExport('UninstallNeedRestart', 'Boolean', True, False, '', 0);
+    CodeCompiler.AddExport('InitializeUninstallProgressForm', '0', True, False, '', 0);
 
     CodeStr := CodeText.Text;
     { Remove trailing CR-LF so that ROPS will never report an error on

+ 5 - 5
Projects/Install.pas

@@ -140,10 +140,10 @@ begin
   end;
   SetAppTaskbarProgressValue(NewPosition.Lo, WizardForm.ProgressGauge.Max);
 
-  if (CodeRunner <> nil) and CodeRunner.FunctionExists('CurInstallProgressChanged') then begin
+  if (CodeRunner <> nil) and CodeRunner.FunctionExists('CurInstallProgressChanged', True) then begin
     try
       CodeRunner.RunProcedure('CurInstallProgressChanged', [NewPosition.Lo,
-        WizardForm.ProgressGauge.Max], False);
+        WizardForm.ProgressGauge.Max], True, False);
     except
       Log('CurInstallProgressChanged raised an exception.');
       Application.HandleException(nil);
@@ -721,7 +721,7 @@ var
       { Also see SetPreviousData in ScriptFunc.pas }
       if CodeRunner <> nil then begin
         try
-          CodeRunner.RunProcedure('RegisterPreviousData', [Integer(H2)], False);
+          CodeRunner.RunProcedure('RegisterPreviousData', [Integer(H2)], True, False);
         except
           Log('RegisterPreviousData raised an exception.');
           Application.HandleException(nil);
@@ -2916,10 +2916,10 @@ var
 
   procedure ProcessNeedRestartEvent;
   begin
-    if (CodeRunner <> nil) and CodeRunner.FunctionExists('NeedRestart') then begin
+    if (CodeRunner <> nil) and CodeRunner.FunctionExists('NeedRestart', True) then begin
       if not NeedsRestart then begin
         try
-          if CodeRunner.RunBooleanFunction('NeedRestart', [''], False, False) then begin
+          if CodeRunner.RunBooleanFunction('NeedRestart', [''], True, False, False) then begin
             NeedsRestart := True;
             Log('Will restart because NeedRestart returned True.');
           end;

+ 11 - 10
Projects/Main.pas

@@ -375,7 +375,7 @@ end;
 class function TDummyClass.EvalInstallIdentifier(Sender: TSimpleExpression;
   const Name: String; const Parameters: array of const): Boolean;
 begin
-  CodeRunner.RunProcedure(AnsiString(Name), Parameters, True);
+  CodeRunner.RunProcedure(AnsiString(Name), Parameters, False, True);
   Result := True;  { Result doesn't matter }
 end;
 
@@ -461,7 +461,7 @@ end;
 class function TDummyClass.EvalCheckIdentifier(Sender: TSimpleExpression;
   const Name: String; const Parameters: array of const): Boolean;
 begin
-  Result := CodeRunner.RunBooleanFunction(AnsiString(Name), Parameters, True, False);
+  Result := CodeRunner.RunBooleanFunction(AnsiString(Name), Parameters, False, True, False);
 end;
 
 function EvalCheck(const Expression: String): Boolean;
@@ -1994,7 +1994,7 @@ begin
       AllowCodeRegisterExtraCloseApplicationsResource := True;
       try
         try
-          CodeRunner.RunProcedure('RegisterExtraCloseApplicationsResources', [''], False);
+          CodeRunner.RunProcedure('RegisterExtraCloseApplicationsResources', [''], True, False);
         except
           Log('RegisterExtraCloseApplicationsResources raised an exception.');
           Application.HandleException(nil);
@@ -2679,7 +2679,7 @@ var
       if shPassword in SetupHeader.Options then
         PasswordOk := TestPassword(S);
       if not PasswordOk and (CodeRunner <> nil) then
-        PasswordOk := CodeRunner.RunBooleanFunction('CheckPassword', [S], False, PasswordOk);
+        PasswordOk := CodeRunner.RunBooleanFunction('CheckPassword', [S], True, False, PasswordOk);
 
       if PasswordOk then begin
         Result := False;
@@ -3198,6 +3198,7 @@ begin
   if SetupHeader.CompiledCodeText <> '' then begin
     CodeRunner := TScriptRunner.Create();
     try
+      CodeRunner.NamingAttribute := 'Event';
       CodeRunner.OnLog := CodeRunnerOnLog;
       CodeRunner.OnLogFmt := CodeRunnerOnLogFmt;
       CodeRunner.OnDllImport := CodeRunnerOnDllImport;
@@ -3206,17 +3207,17 @@ begin
       CodeRunner.OnException := CodeRunnerOnException;
       CodeRunner.LoadScript(SetupHeader.CompiledCodeText, DebugClientCompiledCodeDebugInfo);
       if not NeedPassword then
-        NeedPassword := CodeRunner.FunctionExists('CheckPassword');
+        NeedPassword := CodeRunner.FunctionExists('CheckPassword', True);
       NeedPassword := HandleInitPassword(NeedPassword);
       if not NeedSerial then
-        NeedSerial := CodeRunner.FunctionExists('CheckSerial');
+        NeedSerial := CodeRunner.FunctionExists('CheckSerial', True);
     except
       { Don't let DeinitSetup see a partially-initialized CodeRunner }
       FreeAndNil(CodeRunner);
       raise;
     end;
     try
-      Res := CodeRunner.RunBooleanFunction('InitializeSetup', [''], False, True);
+      Res := CodeRunner.RunBooleanFunction('InitializeSetup', [''], True, False, True);
     except
       Log('InitializeSetup raised an exception (fatal).');
       raise;
@@ -3400,7 +3401,7 @@ begin
       end;
     end;
     try
-      CodeRunner.RunProcedure('DeinitializeSetup', [''], False);
+      CodeRunner.RunProcedure('DeinitializeSetup', [''], True, False);
     except
       Log('DeinitializeSetup raised an exception.');
       Application.HandleException(nil);
@@ -3709,7 +3710,7 @@ begin
   CurStep := AStep;
   if CodeRunner <> nil then begin
     try
-      CodeRunner.RunProcedure('CurStepChanged', [Ord(CurStep)], False);
+      CodeRunner.RunProcedure('CurStepChanged', [Ord(CurStep)], True, False);
     except
       if HandleExceptions then begin
         Log('CurStepChanged raised an exception.');
@@ -3728,7 +3729,7 @@ begin
   WizardForm := TWizardForm.Create(Application);
   if CodeRunner <> nil then begin
     try
-      CodeRunner.RunProcedure('InitializeWizard', [''], False);
+      CodeRunner.RunProcedure('InitializeWizard', [''], True, False);
     except
       Log('InitializeWizard raised an exception (fatal).');
       raise;

+ 138 - 43
Projects/ScriptCompiler.pas

@@ -25,6 +25,7 @@ type
 
   TScriptCompiler = class
     private
+      FNamingAttribute: String;
       FExports, FUsedLines: TList;
       FFunctionsFound: TStringList;
       FScriptText: AnsiString;
@@ -33,17 +34,19 @@ type
       FOnUsedVariable: TScriptCompilerOnUsedVariable;
       FOnError: TScriptCompilerOnError;
       FOnWarning: TScriptCompilerOnWarning;
+      function FindExport(const Name, Decl: String; const IgnoreIndex: Integer): Integer;
       function GetExportCount: Integer;
       procedure PSPositionToLineCol(Position: LongInt; var Line, Col: LongInt);
     public
       constructor Create;
       destructor Destroy; override;
-      procedure AddExport(const Name, Decl: String; const Required: Boolean; const RequiredFilename: String; const RequiredLine: LongInt);
+      procedure AddExport(const Name, Decl: String; const AllowNamingAttribute, Required: Boolean; const RequiredFilename: String; const RequiredLine: LongInt);
       function CheckExports: Boolean;
       function Compile(const ScriptText: String; var CompiledScriptText, CompiledScriptDebugInfo: tbtString): Boolean;
       property ExportCount: Integer read GetExportCount;
       function ExportFound(const Name: String): Boolean;
       function FunctionFound(const Name: String): Boolean;
+      property NamingAttribute: String write FNamingAttribute;
       property OnLineToLineInfo: TScriptCompilerOnLineToLineInfo write FOnLineToLineInfo;
       property OnUsedLine: TScriptCompilerOnUsedLine write FOnUsedLine;
       property OnUsedVariable: TScriptCompilerOnUsedVariable write FOnUsedVariable;
@@ -61,6 +64,7 @@ uses
 type
   TScriptExport = class
     Name, Decl: String;
+    AllowNamingAttribute: Boolean;
     Required: Boolean;
     RequiredFilename: String;
     RequiredLine: LongInt;
@@ -95,13 +99,94 @@ begin
   Result := DllExternalProc(Sender, Decl, Name, tbtstring(TrimRight(S)));
 end;
 
-function PSPascalCompilerOnUses(Sender: TPSPascalCompiler; const Name: tbtstring): Boolean; 
+function PSPascalCompilerOnApplyAttributeToProc(Sender: TPSPascalCompiler; aProc: TPSProcedure; Attr: TPSAttribute): Boolean;
+var
+  ScriptCompiler: TScriptCompiler;
+  AttrValue: String;
+  ScriptExport: TScriptExport;
+  B: Boolean;
+  I: Integer;
+begin
+  ScriptCompiler := TScriptCompiler(Sender.ID);
+  if CompareText(String(Attr.AType.Name), ScriptCompiler.FNamingAttribute) = 0 then begin
+    if (aProc.ClassType <> TPSInternalProcedure) then begin
+      with Sender.MakeError('', ecCustomError, tbtstring('"' + ScriptCompiler.FNamingAttribute + '" attribute cannot be used on external function or procedure')) do
+        SetCustomPos(Attr.DeclarePos, Attr.DeclareRow, Attr.DeclareCol);
+      Result := False;
+    end else if Attr.Count <> 1 then begin
+      with Sender.MakeError('', ecCustomError, tbtstring('"' + ScriptCompiler.FNamingAttribute + '" attribute value not found')) do
+        SetCustomPos(Attr.DeclarePos, Attr.DeclareRow, Attr.DeclareCol);
+      Result := False;
+    end else begin
+      if ScriptCompiler.FindExport(String(TPSInternalProcedure(aProc).Name), '', -1) <> -1 then begin
+        { Don't allow attributes on functions already matching an export so that we don't have to deal with this later. }
+        with Sender.MakeError('', ecCustomError, tbtstring('"' + ScriptCompiler.FNamingAttribute + '" attribute not allowed for function or procedure "' + String(TPSInternalProcedure(aProc).Name) + '"')) do
+          SetCustomPos(Attr.DeclarePos, Attr.DeclareRow, Attr.DeclareCol);
+        Result := False;
+      end else begin
+        AttrValue := String(GetString(Attr.Values[0], B));
+        I := ScriptCompiler.FindExport(AttrValue, String(Sender.MakeDecl(TPSInternalProcedure(aProc).Decl)), -1);
+        if I <> -1 then begin
+          ScriptExport := ScriptCompiler.FExports[I];
+          if not ScriptExport.AllowNamingAttribute then begin
+            with Sender.MakeError('', ecCustomError, tbtstring('"' + ScriptCompiler.FNamingAttribute + '" attribute value "' + AttrValue + '" not allowed')) do
+              SetCustomPos(Attr.DeclarePos, Attr.DeclareRow, Attr.DeclareCol);
+            Result := False;
+          end else begin
+            ScriptExport.Exported := True;
+            Result := True;
+          end;
+        end else if ScriptCompiler.FindExport(AttrValue, '', -1) <> -1 then begin
+          with Sender.MakeError('', ecCustomError, tbtstring('Invalid function or procedure prototype for attribute value "' + AttrValue + '"')) do
+            SetCustomPos(TPSInternalProcedure(aProc).DeclarePos, TPSInternalProcedure(aProc).DeclareRow, TPSInternalProcedure(aProc).DeclareCol);
+          Result := False;
+        end else begin
+          with Sender.MakeError('', ecCustomError, tbtstring('"' + ScriptCompiler.FNamingAttribute + '" attribute value "' + AttrValue + '"  invalid')) do
+            SetCustomPos(Attr.DeclarePos, Attr.DeclareRow, Attr.DeclareCol);
+          Result := False;
+        end;
+      end;
+    end;
+  end else
+    Result := True;
+end;
+
+function PSPascalCompilerOnApplyAttributeToType(Sender: TPSPascalCompiler; aType: TPSType; Attr: TPSAttribute): Boolean;
+var
+  NamingAttribute: String;
+begin
+  NamingAttribute := TScriptCompiler(Sender.ID).FNamingAttribute;
+  if (CompareText(String(Attr.AType.Name), NamingAttribute) = 0)  then begin
+    with Sender.MakeError('', ecCustomError, tbtstring('"' + NamingAttribute + '" attribute cannot be used on types')) do
+      SetCustomPos(Attr.DeclarePos, Attr.DeclareRow, Attr.DeclareCol);
+    Result := False;
+  end else
+    Result := True;
+end;
+
+function PSPascalCompilerOnUses(Sender: TPSPascalCompiler; const Name: tbtstring): Boolean;
+var
+  NamingAttribute: String;
 begin
   if Name = 'SYSTEM' then begin
     RegisterDll_Compiletime(Sender);
     Sender.OnExternalProc := PSPascalCompilerOnExternalProc;
     ScriptClassesLibraryRegister_C(Sender);
     ScriptFuncLibraryRegister_C(Sender);
+    NamingAttribute := TScriptCompiler(Sender.ID).FNamingAttribute;
+    if NamingAttribute <> '' then begin
+      with Sender.AddAttributeType do
+      begin
+        OrgName := tbtstring(NamingAttribute);
+        with AddField do
+        begin
+          FieldOrgName := 'Name';
+          FieldType := Sender.FindType('String');
+        end;
+        OnApplyAttributeToProc := PSPascalCompilerOnApplyAttributeToProc;
+        OnApplyAttributeToType := PSPascalCompilerOnApplyAttributeToType;
+      end;
+    end;
     Result := True;
   end else begin
     Sender.MakeError('', ecUnknownIdentifier, '');
@@ -109,34 +194,25 @@ begin
   end;
 end;
 
-function PSPascalCompilerOnExportCheck(Sender: TPSPascalCompiler; Proc: TPSInternalProcedure; const ProcDecl: tbtstring): Boolean; 
+function PSPascalCompilerOnExportCheck(Sender: TPSPascalCompiler; Proc: TPSInternalProcedure; const ProcDecl: tbtstring): Boolean;
 var
-  ScriptExports: TList;
+  ScriptCompiler: TScriptCompiler;
   ScriptExport: TScriptExport;
-  NameFound: Boolean;
   I: Integer;
 begin
-  TScriptCompiler(Sender.ID).FFunctionsFound.Add(String(Proc.Name));
-  ScriptExports := TScriptCompiler(Sender.ID).FExports;
+  ScriptCompiler := TScriptCompiler(Sender.ID);
+
+  ScriptCompiler.FFunctionsFound.Add(String(Proc.Name));
 
   { Try and see if the [Code] function matches an export name and if so,
     see if one of the prototypes for that name matches }
 
-  NameFound := False;
-
-  for I := 0 to ScriptExports.Count-1 do begin
-    ScriptExport := ScriptExports[I];
-    if CompareText(ScriptExport.Name, String(Proc.Name)) = 0 then begin
-      NameFound := True;
-      if CompareText(ScriptExport.Decl, String(ProcDecl)) = 0 then begin
-        ScriptExport.Exported := True;
-        Result := True;
-        Exit;
-      end;
-    end;
-  end;
-
-  if NameFound then begin
+  I := ScriptCompiler.FindExport(String(Proc.Name), String(Procdecl), -1);
+  if I <> -1 then begin
+    ScriptExport := ScriptCompiler.FExports[I];
+    ScriptExport.Exported := True;
+    Result := True;
+  end else if ScriptCompiler.FindExport(String(Proc.Name), '', -1) <> -1 then begin
     with Sender.MakeError('', ecCustomError, tbtstring(Format('Invalid prototype for ''%s''', [Proc.OriginalName]))) do
       SetCustomPos(Proc.DeclarePos, Proc.DeclareRow, Proc.DeclareCol);
     Result := False;
@@ -274,26 +350,30 @@ begin
   Col := Position;
 end;
 
-procedure TScriptCompiler.AddExport(const Name, Decl: String; const Required: Boolean; const RequiredFilename: String; const RequiredLine: LongInt);
+procedure TScriptCompiler.AddExport(const Name, Decl: String; const AllowNamingAttribute, Required: Boolean; const RequiredFilename: String; const RequiredLine: LongInt);
 var
   ScriptExport: TScriptExport;
   I: Integer;
 begin
-  for I := 0 to FExports.Count-1 do begin
+  if AllowNamingAttribute and not ((Pos('0', Decl) = 1) or (Pos('Boolean', Decl) = 1)) then
+    raise Exception.Create('Naming attributes only supported on procedures and boolean functions.');
+
+  I := FindExport(Name, Decl, -1);
+  if I <> -1 then begin
     ScriptExport := FExports[I];
-    if (CompareText(ScriptExport.Name, Name) = 0) and (CompareText(ScriptExport.Decl, Decl) = 0) then begin
-      if Required and not ScriptExport.Required then begin
-        ScriptExport.Required := True;
-        ScriptExport.RequiredFilename := RequiredFilename;
-        ScriptExport.RequiredLine := RequiredLine;
-      end;
-      Exit;
+    if Required and not ScriptExport.Required then begin
+      ScriptExport.Required := True;
+      ScriptExport.RequiredFilename := RequiredFilename;
+      ScriptExport.RequiredLine := RequiredLine;
     end;
+    ScriptExport.AllowNamingAttribute := ScriptExport.AllowNamingAttribute and AllowNamingAttribute;
+    Exit;
   end;
 
   ScriptExport := TScriptExport.Create();
   ScriptExport.Name := Name;
   ScriptExport.Decl := Decl;
+  ScriptExport.AllowNamingAttribute := AllowNamingAttribute;
   ScriptExport.Required := Required;
   if Required then begin
     ScriptExport.RequiredFilename := RequiredFilename;
@@ -302,12 +382,28 @@ begin
   FExports.Add(ScriptExport);
 end;
 
+function TScriptCompiler.FindExport(const Name, Decl: String; const IgnoreIndex: Integer): Integer;
+var
+  ScriptExport: TScriptExport;
+  I: Integer;
+begin
+  for I := 0 to FExports.Count-1 do begin
+    ScriptExport := FExports[I];
+    if ((Name = '') or (CompareText(ScriptExport.Name, Name) = 0)) and
+       ((Decl = '') or (CompareText(ScriptExport.Decl, Decl) = 0)) and
+       ((IgnoreIndex = -1) or (I <> IgnoreIndex)) then begin
+      Result := 0;
+      Exit;
+    end;
+  end;
+  Result := -1;
+end;
+
 function TScriptCompiler.CheckExports: Boolean;
 var
-  ScriptExport, ScriptExport2: TScriptExport;
-  I, J: Integer;
+  ScriptExport: TScriptExport;
+  I: Integer;
   Msg: String;
-  NameFound: Boolean;
 begin
   Result := True;
   for I := 0 to FExports.Count-1 do begin
@@ -315,15 +411,7 @@ begin
     if ScriptExport.Required and not ScriptExport.Exported then begin
       if Assigned(FOnError) then begin
         { Either the function wasn't present or it was present but matched another export }
-        NameFound := False;
-        for J := 0 to FExports.Count-1 do begin
-          ScriptExport2 := FExports[J];
-          if (I <> J) and (CompareText(ScriptExport.Name, ScriptExport2.Name) = 0) then begin
-            NameFound := True;
-            Break;
-          end;
-        end;
-        if NameFound then
+        if FindExport(ScriptExport.Name, '', I) <> -1 then
           Msg := Format('Required function or procedure ''%s'' found but not with a compatible prototype', [ScriptExport.Name])
         else
           Msg := Format('Required function or procedure ''%s'' not found', [ScriptExport.Name]);
@@ -340,6 +428,7 @@ var
   PSPascalCompiler: TPSPascalCompiler;
   L, Line, Col: LongInt;
   Filename, Msg: String;
+  I: Integer;
 begin
   Result := False;
 
@@ -349,6 +438,10 @@ begin
   FScriptText := ScriptText;
 {$ENDIF}
 
+  for I := 0 to FExports.Count-1 do
+    TScriptExport(FExports[I]).Exported := False;
+  FFunctionsFound.Clear;
+
   PSPascalCompiler := TPSPascalCompiler.Create();
 
   try
@@ -360,6 +453,8 @@ begin
     PSPascalCompiler.AllowDuplicateRegister := False;
     PSPascalCompiler.UTF8Decode := True;
 {$ENDIF}
+    PSPascalCompiler.AttributesOpenTokenID := CSTI_Less;
+    PSPascalCompiler.AttributesCloseTokenID := CSTI_Greater;
 
     PSPascalCompiler.OnUses := PSPascalCompilerOnUses;
     PSPascalCompiler.OnExportCheck := PSPascalCompilerOnExportCheck;

+ 94 - 47
Projects/ScriptRunner.pas

@@ -26,6 +26,7 @@ type
 
   TScriptRunner = class
     private
+      FNamingAttribute: String;
       FPSExec: TPSDebugExec;
       FClassImporter: TPSRuntimeClassImporter;
       FOnLog: TScriptRunnerOnLog;
@@ -34,6 +35,7 @@ type
       FOnDebug: TScriptRunnerOnDebug;
       FOnDebugIntermediate: TScriptRunnerOnDebugIntermediate;
       FOnException: TScriptRunnerOnException;
+      function GetProcNos(const Name: AnsiString; const CheckNamingAttribute: Boolean; const ProcNos: TPSList): Integer;
       procedure Log(const S: String);
       procedure LogFmt(const S: String; const Args: array of const);
       procedure RaisePSExecException;
@@ -44,12 +46,13 @@ type
       constructor Create;
       destructor Destroy; override;
       procedure LoadScript(const CompiledScriptText, CompiledScriptDebugInfo: AnsiString);
-      function FunctionExists(const Name: AnsiString): Boolean;
-      procedure RunProcedure(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean);
-      function RunBooleanFunction(const Name: AnsiString; const Parameters: array of Const; const MustExist, Default: Boolean): Boolean;
+      function FunctionExists(const Name: AnsiString; const CheckNamingAttribute: Boolean): Boolean;
+      procedure RunProcedure(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute, MustExist: Boolean);
+      function RunBooleanFunction(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute, MustExist, Default: Boolean): Boolean;
       function RunIntegerFunction(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean; const Default: Integer): Integer;
       function RunStringFunction(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean; const Default: String): String;
       function EvaluateUsedVariable(const Param1, Param2, Param3: LongInt; const Param4: AnsiString): String;
+      property NamingAttribute: String write FNamingAttribute;
       property OnLog: TScriptRunnerOnLog read FOnLog write FOnLog;
       property OnLogFmt: TScriptRunnerOnLogFmt read FOnLogFmt write FOnLogFmt;
       property OnDllImport: TScriptRunnerOnDllImport read FOnDllImport write FOnDllImport;
@@ -356,9 +359,43 @@ begin
   end;
 end;
 
-function TScriptRunner.FunctionExists(const Name: AnsiString): Boolean;
+function TScriptRunner.GetProcNos(const Name: AnsiString; const CheckNamingAttribute: Boolean; const ProcNos: TPSList): Integer;
+var
+  MainProcNo, ProcNo: Cardinal;
+  Proc: PIFProcRec;
+  Attr: TPSRuntimeAttribute;
+begin
+  Result := 0;
+
+  MainProcNo := FPSExec.GetProc(Name);
+  if MainProcNo <> Cardinal(-1) then begin
+    if ProcNos <> nil then
+      ProcNos.Add(Pointer(MainProcNo));
+    Inc(Result);
+  end;
+
+  if CheckNamingAttribute and (FNamingAttribute <> '') then begin
+    for ProcNo := 0 to FPSExec.GetProcCount-1 do begin
+      if ProcNo <> MainProcNo then begin
+        Proc := FPSExec.GetProcNo(ProcNo);
+        if Proc.Attributes.Count > 0 then begin
+          Attr := Proc.Attributes.FindAttribute(AnsiString(FNamingAttribute));
+          if (Attr <> nil) and (Attr.ValueCount = 1) and
+             ({$IFDEF UNICODE} ((Attr.Value[0].FType.BaseType = btUnicodeString) and (CompareText(PPSVariantUString(Attr.Value[0]).Data, Name) = 0)) or {$ENDIF}
+              ((Attr.Value[0].FType.BaseType = btString) and (CompareText(PPSVariantAString(Attr.Value[0]).Data, Name) = 0))) then begin
+            if ProcNos <> nil then
+              ProcNos.Add(Pointer(ProcNo));
+            Inc(Result);
+          end;
+        end;
+      end;
+    end;
+  end;
+end;
+
+function TScriptRunner.FunctionExists(const Name: AnsiString; const CheckNamingAttribute: Boolean): Boolean;
 begin
-  Result := FPSExec.GetProc(Name) <> Cardinal(-1);
+  Result := GetProcNos(Name, CheckNamingAttribute, nil) <> 0;
 end;
 
 procedure WriteBackParameters(const Parameters: array of Const; const Params: TPSList);
@@ -371,54 +408,66 @@ begin
       Boolean(Parameters[I].VPointer^) := (PPSVariantU8(Params[High(Parameters)-I]).Data = 1);
 end;
 
-procedure TScriptRunner.RunProcedure(const Name: AnsiString; const Parameters: array of Const; const MustExist: Boolean);
+procedure TScriptRunner.RunProcedure(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute, MustExist: Boolean);
 var
-  ProcNo: Cardinal;
-  Params: TPSList;
+  ProcNos, Params: TPSList;
+  I: Integer;
 begin
-  ProcNo := FPSExec.GetProc(Name);
-  if ProcNo <> Cardinal(-1) then begin
-    Params := TPSList.Create();
-    try
-      SetPSExecParameters(Parameters, Params);
-      FPSExec.RunProc(Params, ProcNo);
-      WriteBackParameters(Parameters, Params);
-    finally
-      FreePSVariantList(Params);
+  ProcNos := TPSList.Create;
+  try
+    if GetProcNos(Name, CheckNamingAttribute, ProcNos) <> 0 then begin
+      for I := 0 to ProcNos.Count-1 do begin
+        Params := TPSList.Create();
+        try
+          SetPSExecParameters(Parameters, Params);
+          FPSExec.RunProc(Params, Cardinal(ProcNos[I]));
+          WriteBackParameters(Parameters, Params);
+
+          RaisePSExecException;
+        finally
+          FreePSVariantList(Params);
+        end;
+      end;
+    end else begin
+      if MustExist then
+        ShowPSExecError(erCouldNotCallProc);
     end;
-
-    RaisePSExecException;
-  end else begin
-    if MustExist then
-      ShowPSExecError(erCouldNotCallProc);
+  finally
+    ProcNos.Free;
   end;
 end;
 
-function TScriptRunner.RunBooleanFunction(const Name: AnsiString; const Parameters: array of Const; const MustExist, Default: Boolean): Boolean;
+function TScriptRunner.RunBooleanFunction(const Name: AnsiString; const Parameters: array of Const; const CheckNamingAttribute, MustExist, Default: Boolean): Boolean;
 var
-  ProcNo: Cardinal;
-  Params: TPSList;
+  ProcNos, Params: TPSList;
   Res: PPSVariant;
+  I: Integer;
 begin
-  Result := Default;
-
-  ProcNo := FPSExec.GetProc(Name);
-  if ProcNo <> Cardinal(-1) then begin
-    Params := TPSList.Create();
-    try
-      SetPSExecParameters(Parameters, Params);
-      SetPSExecReturnValue(Params, btU8, Res);
-      FPSExec.RunProc(Params, ProcNo);
-      WriteBackParameters(Parameters, Params);
-
-      RaisePSExecException;
-      Result := PPSVariantU8(Res).Data = 1;
-    finally
-      FreePSVariantList(Params);
+  ProcNos := TPSList.Create;
+  try
+    if GetProcNos(Name, CheckNamingAttribute, ProcNos) <> 0 then begin
+      Result := True;
+      for I := 0 to ProcNos.Count-1 do begin
+        Params := TPSList.Create();
+        try
+          SetPSExecParameters(Parameters, Params);
+          SetPSExecReturnValue(Params, btU8, Res);
+          FPSExec.RunProc(Params, Cardinal(ProcNos[I]));
+          WriteBackParameters(Parameters, Params);
+
+          RaisePSExecException;
+          Result := Result and (PPSVariantU8(Res).Data = 1); { Don't break on Result = False: need to call all procs always. }
+        finally
+          FreePSVariantList(Params);
+        end;
+      end;
+    end else begin
+      if MustExist then
+        ShowPSExecError(erCouldNotCallProc);
+      Result := Default;
     end;
-  end else begin
-    if MustExist then
-      ShowPSExecError(erCouldNotCallProc);
+  finally
+    ProcNos.Free;
   end;
 end;
 
@@ -428,8 +477,6 @@ var
   Params: TPSList;
   Res: PPSVariant;
 begin
-  Result := Default;
-
   ProcNo := FPSExec.GetProc(Name);
   if ProcNo <> Cardinal(-1) then begin
     Params := TPSList.Create();
@@ -447,6 +494,7 @@ begin
   end else begin
     if MustExist then
       ShowPSExecError(erCouldNotCallProc);
+    Result := Default;
   end;
 end;
 
@@ -456,8 +504,6 @@ var
   Params: TPSList;
   Res: PPSVariant;
 begin
-  Result := Default;
-
   ProcNo := FPSExec.GetProc(Name);
   if ProcNo <> Cardinal(-1) then begin
     Params := TPSList.Create();
@@ -483,6 +529,7 @@ begin
   end else begin
     if MustExist then
       ShowPSExecError(erCouldNotCallProc);
+    Result := Default;
   end;
 end;
 

+ 6 - 6
Projects/Uninstall.pas

@@ -98,7 +98,7 @@ begin
   UninstallProgressForm.Initialize(Title, UninstLog.AppName);
   if CodeRunner <> nil then begin
     try
-      CodeRunner.RunProcedure('InitializeUninstallProgressForm', [''], False);
+      CodeRunner.RunProcedure('InitializeUninstallProgressForm', [''], True, False);
     except
       Log('InitializeUninstallProgressForm raised an exception (fatal).');
       raise;
@@ -299,7 +299,7 @@ procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep; HandleExcept
 begin
   if CodeRunner <> nil then begin
     try
-      CodeRunner.RunProcedure('CurUninstallStepChanged', [Ord(CurUninstallStep)], False);
+      CodeRunner.RunProcedure('CurUninstallStepChanged', [Ord(CurUninstallStep)], True, False);
     except
       if HandleException then begin
         Log('CurUninstallStepChanged raised an exception.');
@@ -603,7 +603,7 @@ begin
       try
         if CodeRunner <> nil then begin
           try
-            Res := CodeRunner.RunBooleanFunction('InitializeUninstall', [''], False, True);
+            Res := CodeRunner.RunBooleanFunction('InitializeUninstall', [''], True, False, True);
           except
             Log('InitializeUninstall raised an exception (fatal).');
             raise;
@@ -654,10 +654,10 @@ begin
         LogFmt('Removed all? %s', [SYesNo[RemovedAll]]);
 
         UninstallNeedsRestart := UninstLog.NeedRestart or (ufAlwaysRestart in UninstLog.Flags);
-        if (CodeRunner <> nil) and CodeRunner.FunctionExists('UninstallNeedRestart') then begin
+        if (CodeRunner <> nil) and CodeRunner.FunctionExists('UninstallNeedRestart', True) then begin
           if not UninstallNeedsRestart then begin
             try
-              if CodeRunner.RunBooleanFunction('UninstallNeedRestart', [''], False, False) then begin
+              if CodeRunner.RunBooleanFunction('UninstallNeedRestart', [''], True, False, False) then begin
                 UninstallNeedsRestart := True;
                 Log('Will restart because UninstallNeedRestart returned True.');
               end;
@@ -719,7 +719,7 @@ begin
       end;
       if CodeRunner <> nil then begin
         try
-          CodeRunner.RunProcedure('DeinitializeUninstall', [''], False);
+          CodeRunner.RunProcedure('DeinitializeUninstall', [''], True, False);
         except
           Log('DeinitializeUninstall raised an exception.');
           ShowExceptionMsg;

+ 9 - 9
Projects/Wizard.pas

@@ -431,7 +431,7 @@ begin
     WizardUserInfoName := UserInfoNameEdit.Text;
     WizardUserInfoOrg := UserInfoOrgEdit.Text;
     WizardUserInfoSerial := UserInfoSerialEdit.Text;
-    Result := CodeRunner.RunBooleanFunction('CheckSerial', [UserInfoSerialEdit.Text], True, False)
+    Result := CodeRunner.RunBooleanFunction('CheckSerial', [UserInfoSerialEdit.Text], True, True, False)
   end else
     Result := True;
 end;
@@ -1633,7 +1633,7 @@ begin
   if not PreviousInstallCompleted(WizardComponents, WizardTasks) then begin
     Result := ExpandSetupMessage(msgPreviousInstallNotCompleted);
     PrepareToInstallNeedsRestart := True;
-  end else if (CodeRunner <> nil) and CodeRunner.FunctionExists('PrepareToInstall') then begin
+  end else if (CodeRunner <> nil) and CodeRunner.FunctionExists('PrepareToInstall', True) then begin
     SetCurPage(wpPreparing);
     BackButton.Visible := False;
     NextButton.Visible := False;
@@ -1837,7 +1837,7 @@ procedure TWizardForm.UpdatePage(const PageID: Integer);
         MemoTasksInfo := '';
       SelectedTasks.Free();
 
-      if (CodeRunner <> nil) and CodeRunner.FunctionExists('UpdateReadyMemo') then begin
+      if (CodeRunner <> nil) and CodeRunner.FunctionExists('UpdateReadyMemo', False) then begin
         try
           ReadyMemo.Lines.Text := CodeRunner.RunStringFunction('UpdateReadyMemo',
             [Space, SNewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo,
@@ -2003,7 +2003,7 @@ begin
 
   try
     if CodeRunner <> nil then
-      CodeRunner.RunProcedure('CurPageChanged', [CurPageID], False);
+      CodeRunner.RunProcedure('CurPageChanged', [CurPageID], True, False);
   except
     Application.HandleException(Self);
   end;
@@ -2042,7 +2042,7 @@ begin
     if not Result then begin
       try
         if CodeRunner <> nil then
-          Result := CodeRunner.RunBooleanFunction('ShouldSkipPage', [PageID], False, Result);
+          Result := CodeRunner.RunBooleanFunction('ShouldSkipPage', [PageID], True, False, Result);
       except
         Application.HandleException(Self);
       end;
@@ -2063,7 +2063,7 @@ procedure TWizardForm.NextButtonClick(Sender: TObject);
     if shPassword in SetupHeader.Options then
       Result := TestPassword(S);
     if not Result and (CodeRunner <> nil) then
-      Result := CodeRunner.RunBooleanFunction('CheckPassword', [S], False, Result);
+      Result := CodeRunner.RunBooleanFunction('CheckPassword', [S], True, False, Result);
 
     if Result then begin
       NeedPassword := False;
@@ -2228,7 +2228,7 @@ begin
     Exit;
 
   if CodeRunner <> nil then
-    if CodeRunner.RunBooleanFunction('NextButtonClick', [CurPageID], False, True) = False then
+    if CodeRunner.RunBooleanFunction('NextButtonClick', [CurPageID], True, False, True) = False then
       Exit;
 
   { Go to the next page, or close wizard if it was on the last page }
@@ -2328,7 +2328,7 @@ begin
     Exit;
 
   if CodeRunner <> nil then
-    if CodeRunner.RunBooleanFunction('BackButtonClick', [CurPageID], False, True) = False then
+    if CodeRunner.RunBooleanFunction('BackButtonClick', [CurPageID], True, False, True) = False then
       Exit;
 
   PrevPageID := GetPreviousPageID;
@@ -2350,7 +2350,7 @@ begin
 
   if CodeRunner <> nil then
     CodeRunner.RunProcedure('CancelButtonClick', [CurPageID, @ACancel,
-      @AConfirm], False);
+      @AConfirm], True, False);
 end;
 
 procedure TWizardForm.FormClose(Sender: TObject; var Action: TCloseAction);