I'm currently working on an exercise of testing Boolean value from user input, as being presented below:
function ReadBoolean(prompt: String): Boolean;
var
choice: String;
exit: boolean;
begin
repeat
begin
WriteLn(prompt);
ReadLn(choice);
case choice of
'yes','y','t','true': exit := true;
'no','n','f','false': exit := false;
else
WriteLn('Not a boolean input. Enter again: ');
end;
end;
until exit=true or exit=false;
result := exit;
end;
It is expected to keep looping asking for value until it receives the input from specified string, however at my first attempt when I try to input 'fred' the boolean variable is automatically assigned as TRUE and exit the function.
Any helps would be very appreciated.
As my understanding, you only want the loop to end when user enters some specific strings.
It can be achieved by modifying the until condition like this:
choice='yes' or choice='y' or choice='t' or choice='true' or choice='no' or choice='n' or choice='f' or choice='false'
Or alternatively, create an indefinite loop and break it when user enters an expected string:
while true do
...
'yes','y','t','true':
begin
exit := true;
break;
end;
'no','n','f','false':
begin
exit := false;
break;
end;
...
end;
Your loop terminates when exit=true or exit=false. As exit can only ever be one of those two values, it will always meet that condition, so it will never run your loop.
But also, consider explicitly setting the value of exit := false before starting your loop.
What you're asking here is "nullable" boolean thing (value is true, value is false, value is not provided). As far as I know it's not implemented in any Pascal dialect. So that, you have to split your indication into two separate flags: a) is there any well-formed input provided by user; b) is the input is recognized as true or false
function ReadBoolean(prompt: String): Boolean;
var
choice: String;
exit: boolean;
recognized: boolean; { this is our termination flag }
begin
recognized := false; { we place it to false initially as no user input recognized yet }
repeat
begin
WriteLn(prompt);
ReadLn(choice);
case choice of
'yes','y','t','true': begin exit := true; recognized := true; end; { we mark it as recognized }
'no','n','f','false': begin exit := false; recognized := true; end; { we mark it as recognized }
else
WriteLn('Not a boolean input. Enter again: ');
end;
end;
until not recognized; { we keep asking for user input until known input provided }
result := exit;
end;
Related
I use this code to ask for a password:
Inno Setup - Move the password page before the welcome page (first page)
And this code for custom language selector:
Inno Setup - Language selector with VCL Styles
When I merge them, it does not work.
I need password before that the language selector, so this is no correct:
function InitializeSetup(): Boolean;
var
Language: string;
begin
Result := True;
Language := ExpandConstant('{param:LANG}');
if Language = '' then
begin
Log('No language specified, showing language dialog');
SelectLanguage();
Result := False;
Exit;
end
else
begin
Log('Language specified, proceeding with installation');
Result := AskPassword();
end;
end;
And this way, with an incorrect password the setup continues.
function InitializeSetup(): Boolean;
var
Language: string;
begin
Result := True;
Language := ExpandConstant('{param:LANG}');
if Language = '' then
begin
Result := AskPassword();
Log('No language specified, showing language dialog');
SelectLanguage();
Result := False;
Exit;
end
else
begin
Log('Language specified, proceeding with installation');
end;
end;
Inno Setup 6
Inno Setup 6 has event attributes features that helps solving this problem.
Just make sure that each of your event implementation have an unique name, e.g. appending unique suffix. And add event attribute with the name of the implemented event.
[Code]
function InitializeSetup(): Boolean;
begin
Result := ...
end;
<event('InitializeSetup')>
function InitializeSetup2(): Boolean;
begin
Result := ...
end;
Inno Setup 5
In general, the easiest is to keep both implementations of the event function separate and add one wrapper implementation that call both.
function InitializeSetup1(): Boolean;
var
Language: string;
begin
Result := True;
Language := ExpandConstant('{param:LANG}');
if Language = '' then
begin
Log('No language specified, showing language dialog');
SelectLanguage();
Result := False;
Exit;
end
else
begin
Log('Language specified, proceeding with installation');
Result := True;
end;
end;
function InitializeSetup2(): Boolean;
begin
Result := AskPassword();
end;
function InitializeSetup(): Boolean;
begin
{ Order the calls the way you want the checks to be performed }
Result :=
InitializeSetup2() and
InitializeSetup1();
end;
For more general discussion of the problem, see
Merging event function (InitializeWizard) implementations from different sources
Though in your specific case, it's more complicated, as you will also need to pass the password from the first instance to the other, similarly to how the language is passed from the first instance to the other.
So actually, the InitializeSetup2 (password) implementation will have to be similar like the InitializeSetup1 (language), not to ask for the password again.
I actually do not really understand, why you complicate the things so much by not asking for language before the password. It would actually make sense. To get a localized password prompt.
I'm following an internet course on the basics of programming. After making a diagram I convert it to code, right now this is PASCAL language.
I'm having a problem with procedures and can't find an answer, nor in the course, nor with some google-ing.
I want to get a variavble back form a procedure. Right now iIhave a working piece of code but I think this is not the good way of working. Here's an extract of the code:
program WELKEWAGEN;
// declare your variables here
var T, N, KM, vari, prijsDW, prijsBW, jrenGEBR, taksDW, taksBW, prijsB, verbrBW, prijsD, verbrDW : real;
procedure OPHALEN(para : string);
begin
repeat
writeln('geef de ', para , ' op');
readln(vari);
until (vari > 0);
end;
begin
//this is the main program but there is more code ofcourse
OPHALEN('prijs benzinewagen');
prijsBW := vari;
//...
end.
Now the internet course says I should program it like this:
begin
//...
prijsBW := OPHALEN('prijs benzinewagen');
//...
end.
But this is not working.
I get following errors:
WELKEWAGEN.pas(24,14) Error: Incompatible types: got "untyped" expected "Real"
WELKEWAGEN.pas(50) Fatal: There were 1 errors compiling module, stopping
pas(24,14) is this line: prijsBW := OPHALEN('prijs benzinewagen');
Procedures don't return values, so the syntax
prijsBW := OPHALEN('prijs benzinewagen');
is invalid.
If you want to return a value, you need to define a function instead:
function OPHALEN(para : string): Real;
var
Res: Real;
begin
Res := 0;
repeat
writeln('geef de ', para , ' op');
readln(Res);
until (Res > 0);
OPHALEN := Res;
end;
Note that the (bad) global variables you're using mean you don't have to return anything at all, because a procedure can access and change that global variable directly (but you have no way of knowing when the procedure is finished):
procedure OPHALEN(para : string);
begin
vari := 0;
repeat
writeln('geef de ', para , ' op');
readln(vari);
until (vari > 0);
end;
Modern Pascal dialects (such as Delphi and FreePascal) allow a cleaner syntax for the return value of functions by using an automatically declared function result variable of the proper type for you, named Result (because that's what it is - the result of the function):
function OPHALEN(para : string): Real;
begin
Result := 0;
repeat
writeln('geef de ', para , ' op');
readln(Result);
until (Result > 0);
end;
If you need to return multiple values, you can use var parameters, which allow them to be changed inside the function.
procedure OPHALEN(para: string; var RetVal: Real);
begin
RetVal := 0;
repeat
writeln('geef de ', para , ' op');
readln(RetVal);
until (RetVal > 0);
end;
Your original code (and the examples I've provided above) all fail to allow the user to cancel, BTW. There should be some way to exit the loop for the user; otherwise, your code just endlessly loops, writing para to the screen and then waiting for input. This has a tendency to annoy users.
I can successfully query for a known Key's value, using the code below. How can I recursively search the subkeys (in my example below, all subkeys within the Uninstall folder) for a particular data's value? My aim is to see if some particular program is installed, and if not, install it.
function
...(omitted)
var
Res : String;
begin
RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{92EA4162-10D1-418A-91E1-5A0453131A38}','DisplayName', Res);
if Res <> 'A Value' then
begin
// Successfully read the value
MsgBox('Success: ' + Res, mbInformation, MB_OK);
end
end;
The principle is easy, with the RegGetSubkeyNames you'll get an array of subkeys of a certain key and then you just iterate this array and query all the subkeys for the DisplayName value and compare the value (if any) with the searched one.
The following function shows the implementation. Note, that I've removed the Wow6432Node node from the path, so if you really need it, modify the UnistallKey constant in the code:
[Code]
const
UnistallKey = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall';
function IsAppInstalled(const DisplayName: string): Boolean;
var
S: string;
I: Integer;
SubKeys: TArrayOfString;
begin
Result := False;
if RegGetSubkeyNames(HKEY_LOCAL_MACHINE, UnistallKey, SubKeys) then
begin
for I := 0 to GetArrayLength(SubKeys) - 1 do
begin
if RegQueryStringValue(HKEY_LOCAL_MACHINE, UnistallKey + '\' + SubKeys[I],
'DisplayName', S) and (S = DisplayName) then
begin
Result := True;
Exit;
end;
end;
end
else
RaiseException('Opening the uninstall key failed!');
end;
Using Inno Setup 5.5.2 I am trying to conditionally skip selection of the installation directory depending on the existence of a path. Specifically, if the 'D:\' drive is available I want installation to it in a predefined location with no prompts, and if it is not available, provide prompts with a reasonable default.
I have code that works for DefaultDirName, but not for DisableDirPage:
[Code]
const
DefaultDrive = 'D:\';
AppFolder = 'SomeDir';
function GetDefaultDir( Param: String ) : String;
begin
if DirExists( DefaultDrive ) then begin
Result := DefaultDrive + AppFolder;
end else begin
Result := ExpandConstant('{pf}\') + AppFolder;
end;
end;
function DefaultDirValid( Param: String ) : Boolean;
begin
Result := DirExists( DefaultDrive );
end;
[Setup]
; Works as expected
DefaultDirName={code:GetDefaultDir}
...
; Compiler Error - Value of [Setup] section directive "DisableDirPage" is invalid.
DisableDirPage={code:DefaultDirValid}
I have tried using functions for DisableDirPage that return Strings of 'yes' and 'no', as well as Integers of 0 and 1. I have also tried inlining the call to DirExists. All have produced the same compiler error.
My best guess is that it has something to do with the fact that DisableDirPage takes a tri-state yes, no, or auto. Is there a specific type associated with the tri-state logic that needs to be returned? The Inno Help on Scripted Constants only says:
The called function must have 1 String parameter named Param, and must return a String or a Boolean value depending on where the constant is used.
Using the ShouldSkipPage event handler you can skip the directory selection page when the DefaultDrive constant path exists with the following script:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={code:GetDefaultDir}
[Code]
const
DefaultDrive = 'D:\';
AppFolder = 'Some Folder';
function GetDefaultDir(Param: string): string;
begin
Result := DefaultDrive + AppFolder;
if not DirExists(DefaultDrive) then
Result := ExpandConstant('{pf}\') + AppFolder;
end;
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := (PageID = wpSelectDir) and DirExists(DefaultDrive);
end;
For a function to return a value in Pascal the assignment FunctionName := SomeVal; is used. I assume it doesn't stop the function execution in that exact place as return in C does. Is there something similar to C return in Pascal? (I'm using FreePascal compiler)
You can use the Exit procedure.
function Foo (Value : integer) : Integer;
begin
Exit(Value*2);
DoSomethingElse(); // This will never execute
end;
The return code of the end of every program is stored in the EAX register. So you can use Assembly inline on Pascal to return wherever you want to end the program running using!
asm
mov eax,%ERROLEVEL%
end;
I think you can use either the function name itself, "result", or Exit(), but I've only used the result identifier, so don't know if the others will work for you:
function Foo(a, b: Integer): Integer;
begin
result := a + b;
end;
Hope that helps ^^
In canonical pascal (without keyword Exit) you can emulate return via goto:
function Foo (Value : integer) : boolean;
label return;
begin
if Value < 0 then
begin
Foo := false;
goto return;
end;
Calc();
Foo := true;
return:
end;