Validating an input in Pascal (pre release material) - delphi

I'm playing around with the comp 1 pre release material, but I can't seem to asnwer this properly in Pascal compiling in delphi...
This question refers to the function RollBowlDie.
This function has no validation of the input value.
What you have to do....
Make a copy of the Skeleton Program. Add additional statements to the function RollBowlDie so that it ensures that the input BowlDieResult is in the range 1 to 6 before allowing the game to continue and outputs an error message if not.
Add further statements to the function RollBowlDie so that it also checks that the input
BowlDieResult does not cause the program to crash if it is the wrong data type and instead outputs an error message when this is detected.
Test the amended function to show that it does not allow inputs that are out of range.
Test the amended function to show that it does not crash if an incorrect data type is added.
This is the RollBowlDie function.
Function RollBowlDie(VirtualDiceGame : Boolean) : Integer;
Var
BowlDieResult : Integer;
Begin
If VirtualDiceGame
Then BowlDieResult := Random(6) + 1
Else
Begin
Writeln('Please roll the bowling die and then enter your result.');
Writeln;
Writeln('Enter 1 if the result is a 1');
Writeln('Enter 2 if the result is a 2');
Writeln('Enter 3 if the result is a 4');
Writeln('Enter 4 if the result is a 6');
Writeln('Enter 5 if the result is a 0');
Writeln('Enter 6 if the result is OUT');
Writeln;
Write('Result: ');
Readln(BowlDieResult);
Writeln;
End;
RollBowlDie := BowlDieResult;
End;
I have tried putting this in, but the message error message didnt come up, and the program crashed when trying in a letter.
Function RollBowlDie(VirtualDiceGame : Boolean) : Integer;
Var
BowlDieResult : Integer;
Begin
If VirtualDiceGame
Then BowlDieResult := Random(6) + 1
Else
Begin
Repeat
Writeln('Please roll the bowling die and then enter your result.');
Writeln;
Writeln('Enter 1 if the result is a 1');
Writeln('Enter 2 if the result is a 2');
Writeln('Enter 3 if the result is a 4');
Writeln('Enter 4 if the result is a 6');
Writeln('Enter 5 if the result is a 0');
Writeln('Enter 6 if the result is OUT');
Writeln;
Write('Result: ');
Try
Readln(BowlDieResult)
Except
Writeln('Not a valid number')
End;
Writeln;
Until (BowlDieResult >= 1) and (BowlDieResult <= 6);
End;
RollBowlDie := BowlDieResult;
End;
I's not sure how to solve the question, any help would be appreciated greatly!

Probably you need to read a string or a char-typed variable instead of an integer one, and then convert the string/char to integer in an controlled way.

Of course it crashes if you type in a letter ... you're specifically asking to
read an integer, and a letter isn't an integer.
Before commenting more on that issue, why not just say:
writeln ('Please enter the number rolled, or 0 if it is an OUT: ');
rather than having 6 writelns? Also, what if a 3 or 5 was rolled (you're only
giving (somewhat odd) directions for the values 1, 2, 4, 6, and 0 ... leaving
out 3 and 5. Keep in mind that you can't get a 0 from "BowlDieResult := Random(6) + 1".
How do you intend the user to indicate "stop" if you're looping until you have
a value between 1 and 6?
Back to the "how do I read a letter" question...
Use a variable of type "char" (or "packed array of char") to read arbitrary text ...
then, compare the character read to '1', '2', ..., '6', or (say) 'Q' (for Quit).
E.g.:
var
answer : char;
attempts : integer; {prevent infinite loop}
done : boolean;
attempts := 0;
saw_quit := false;
done := false; {loop until we get a good number or a QUIT command}
{Or until 9 attempts have been made to enter a #. }
while not done do
begin
writeln ('Please enter a number (1..6) or Q to quit: ');
readln (answer);
if answer in ['1'..'6'] then
begin {we got a number in range 1..6...}
BowlDieResult := ord (answer) - ord ('0'); {convert char to int}
done := true;
end
else if answer in ['Q', 'q'] then {upper or lower case :) }
begin
saw_quit := true;
done := true;
end
else
begin
writeln ('Sorry, that is not a number from 1 to 6 or a "Q"!');
attempts := attempts + 1;
if attempts > 9 then
begin
writeln ('Sorry, too many mistakes ... assuming QUIT');
saw_quit := true;
done := true;
end;
end;
end; {while not done}
Note: the above not tested for compilation ... I NEVER use Pascal's built-in I/O,
for performance and reliability reasons, so I'm rusty with it.
Note: see http://www.allegro.com/papers/htpp.html
for some philosophy of Pascal programming.
Stan

Related

Has function initialization code changed from Seattle to Tokyo?

I am in the process of upgrading code from Delphi 10 Seattle to Delphi 10.2 Tokyo and get a lot of H2077 hints Value assigned to ... never used on assignments.
(Even in places where these were explicitly added in the past to get rid of 'may not have a value' warnings).
These are all function initialized like:
Result := 0;
...
Or:
Result := ftType1; // where ftType1 is an enumerated type
...
Did the compiler get smarter in detecting these or has something changed regarding the initial return values of functions?
We have always had these hints 'on', and I always build (not compile).
Example function (1) that builds without hints in Seattle,
but gives the hint H2077 Value assigned to 'GetDatabaseDialect' not used on the first Result := 0 line in Tokyo.
function GetDatabaseDialect(DBName, User, Pswd: string) : integer;
var
status: array[1..19] of longint;
szDbName, szDbParam: PANSIChar;
dbHandle : pointer;
rslt: longint;
lDPBBuffer : ANSIString;
lDPBLength : integer;
cItem: ANSIChar;
szRslt: PANSIChar; //array[0..IBResultBufferSize-1] of ANSIChar;
begin
Result := 0;
dbHandle := nil;
// init database parameter block with version number
lDPBBuffer := '';
SetLength(lDPBBuffer, 1);
lDPBBuffer[1] := ANSIChar(isc_dpb_version1);
lDPBLength := 1;
// fill Database Parameter Buffer with user name/password
lDPBBuffer := lDPBBuffer +
ANSIChar(isc_dpb_user_name) +
ANSIChar(Length(User)) +
ANSIString( User );
Inc(lDPBLength, 2 + Length(User));
lDPBBuffer := lDPBBuffer +
ANSIChar(isc_dpb_password) +
ANSIChar(Length(Pswd)) +
ANSIString( Pswd );
Inc(lDPBLength, 2 + Length(Pswd));
//Pointers naar naam + buffer
szDbName := PANSIChar(ANSISTring(DBName));
szDbParam := PANSIChar( lDPBBuffer );
// attach to the database and set dialect
rslt := isc_attach_database(#status, 0, szDbName, #dbHandle, lDPBLength, szDbParam);
if rslt <> 0 then
raise EDatabaseError.Create('Error attaching database! ISC# ' + IntToStr(rslt));
//Haal sql dialect op
szRslt := AllocMem(1000);
try
FillChar( szRslt^, 1000, 0);
cItem := ANSIChar( isc_info_db_SQL_dialect );
rslt := isc_database_info(#status, #DBHandle, 1, #cItem, 1000, szRslt);
if rslt <> 0 then
raise EDatabaseError.Create('Error retrieving database info ! ISC# ' + IntToStr(rslt));
Result := Ord(szRslt[3]); //3e positie is dialect
finally
FreeMem(szRslt);
end;
// Drop the connection to the database
rslt := isc_detach_database(#status, #dbHandle);
if rslt <> 0 then
raise EDatabaseError.Create('Error detaching database! ISC# ' + IntToStr(rslt));
end;
Example (2) from a third party library that does not seem to be optimized for Tokyo,
illustrating the case with enumerated types:
H2077 Value assigned to 'TppTemplate.StreamType' not used
Note that changing the assignment to Result := ftASCII; does not make the hint go away (my initial assumption that it was associated with the first enumeration value was incorrect).
type TppFormatType = (ftBinary, ftASCII);
function TppTemplate.StreamType(aStream: TStream): TppFormatType;
var
lSavePos: Integer;
begin
{save stream position}
lSavePos := aStream.Position;
Result := ftBinary;
try
ComputeOffsetFromStream(aStream);
aStream.Seek(FOffset, soBeginning);
if IsValidASCIISignature(aStream) then
Result := ftASCII
else if IsValidBinarySignature(aStream) then
Result := ftBinary
else
raise EInvalidTemplateError.Create(ppLoadStr(49));
finally
{restore stream position}
aStream.Seek(lSavePos, soBeginning);
end;
end; {function, StreamType}
The common denominator seems to be the Result assignments being in try/finally blocks.
Consider this code with a minimal reproduction of your scenario:
function Bar: Boolean;
begin
Result := Random<0.5;
end;
function Foo: Integer;
begin
Result := 0;
if Bar then
Result := 1
else
raise Exception.Create('');
end;
The compiler, even older versions, emits the following hint:
[dcc32 Hint]: H2077 Value assigned to 'Foo' never used
This is reasonable. The first assignment to Result is pointless and can be removed.
Now consider this variation:
function Foo: Integer;
begin
Result := 0;
try
if Bar then
Result := 1
else
raise Exception.Create('');
finally
end;
end;
Older versions of the compiler no longer emit the hint, but the latest version of the compiler does. This should be considered a compiler defect, for older versions. The two variants of Foo shown above are semantically identical. The compiler would be justified in generating identical code.
As you surmise, the assignment being inside the try/finally block is necessary to trigger the defect in previous versions.
We can conclude that the Embarcadero developers have fixed a defect in Tokyo. You can resolve the hints by removing the spurious initial assignments.
Of course, if your code is to be compiled by older versions of the compiler, as well as by new versions, then you are in a bind. With the code as it stands now, a hint is emitted by new versions of the compiler. Remove the initial assignment and a hint is emitted by old versions of the compiler.

The error invalid Pointer Operation on delphi 7

procedure searchAndReceipt;
var
amt, counter, check: integer;
gtinStore, qtyStore: array of integer;
totalCost: real;
begin
check := 0;
totalCost := 0.0;
write('Enter how many products you are purchasing: ');
repeat
readln(amt);
if (amt > 11) and (amt <= 0) then
writeln ('Please re-enter how many products are you purchasing with a value between 1-10')
else
check:= 1;
until check = 1;
SetLength(gtinStore, amt);
SetLength(qtyStore, amt);
SetLength(receiptArray, amt);
for counter:=1 to amt do
begin
write('Enter a GTIN code: ');
repeat
readln(gtinStore[counter]);
if (gtinStore[counter] >= 99999999) and (gtinStore[counter] <= 1000000) then
writeln ('Please re-enter the Gtin Code with a value of 8 digits')
else
check:= 1;
until check = 1;
check := 0;
write('Enter the Quantity: ');
repeat
readln(qtyStore[counter]);
if (qtyStore[counter] >= 11) and (qtyStore[counter] <= 0) then
writeln ('Please re-enter the quantity with a value between 1-10')
else
check:= 1;
until check = 1;
end;
assign(stockFile,'stockFile.dat');
Reset(stockFile);
counter:=1;
while not EOF(stockFile) do
begin
receiptArray[counter].productName := ('Product Not Found');
receiptArray[counter].productGTIN := 0;
receiptArray[counter].productPrice := 0.0;
inc(counter);
end;
read (stockFile, Stock);
for counter:=1 to amt+1 do
begin
while not EOF(stockFile) do
begin
read (stockFile, Stock);
if Stock.productGTIN = gtinStore[counter] then
receiptArray[counter].productGTIN:= Stock.productGTIN;
receiptArray[counter].productName:= Stock.productName;
receiptArray[counter].productPrice:= Stock.productPrice;
end;
end;
assign(receiptFile, 'receipt.txt');
rewrite(receiptFile);
for counter:= 1 to amt+1 do
begin
if receiptArray[counter].productName = 'Product Not Found' then
begin
writeln(receiptFile, 'GTIN: ', gtinStore[counter]);
writeln(receiptFile, receiptArray[counter].productName);
writeln(receiptFile, '');
end
else
begin
writeln(receiptFile, 'GTIN: ',gtinStore[counter]);
writeln(receiptFile, 'Name: ',receiptArray[counter].productName);
writeln(receiptFile, 'Quantity: ', qtyStore[counter]);
writeln(receiptFile, 'Price: £',receiptArray[counter].productPrice*qtyStore[counter]:4:2);
writeln(receiptFile, '');
totalCost := ((receiptArray[counter].productPrice * qtyStore[counter]) + totalCost);
end;
end;
choices:=1;
end;
begin
choices:= 1;
while choices <> 3 do
begin;
writeln('Press 1 to create the stock file');
writeln('Press 2 to search for an item and print a receipt');
writeln('Press 3 to exit');
write('Choice: ');
readln(choices);
writeln;
case choices of
1: createStock;
2: searchAndReceipt;
end;
end;
end.
I run this procedure (there's another procedure before this that places stock into a file), what this is supposed to do is to take that stock out and place it into a text file... however after I've entered the GTIN number and the quantities of the items my program produces this error
Exception EAccessViolation in module Task_2.exe at 00002550.
Access violation at address 00402550 in module 'Task_2.exe'. Read of address 03491DD4.
within the shell, and a message box pops up saying
Project Task_2.exe raised exception class EInvalidPointer with message 'invalid Pointer Operation'. Process Stopped
Thanks in advance
Dynamic arrays are 0-based, but your code assumes 1-based indexing. Hence you index off the end of the array, and hence the runtime errors. Fix the code by using 0-based indices. That is loop from 0 to N-1 rather than from 1 to N.
Even what you fix that, you have loops that run from 1 to N+1 so you aren't even allocating enough space for your arrays.
You should enable range checking in the compiler options so that the compiler can emit diagnostics code to give you better error messages.

Writing all instances of strings between two other strings to logfile

After looking at Delphi extract string between to 2 tags and trying the code given there by Andreas Rejbrand I realized that I needed a version that wouldn't stop after one tag - my goal is to write all the values that occur between two strings in several .xml files to a logfile.
<screen> xyz </screen> blah blah <screen> abc </screen>
-> giving a logfile with
xyz
abc
... and so on.
What I tried was to delete a portion of the text read by the function, so that when the function repeated, it would go to the next instance of the desired string and then write that to the logfile too until there were no matches left - the boolean function would be true and the function could stop - below the slightly modified function as based on the version in the link.
function ExtractText(const Tag, Text: string): string;
var
StartPos1, StartPos2, EndPos: integer;
i: Integer;
mytext : string;
bFinished : bool;
begin
bFinished := false;
mytext := text;
result := '';
while not bFinished do
begin
StartPos1 := Pos('<' + Tag, mytext);
if StartPos1 = 0 then bFinished := true;
EndPos := Pos('</' + Tag + '>', mytext);
StartPos2 := 0;
for i := StartPos1 + length(Tag) + 1 to EndPos do
if mytext[i] = '>' then
begin
StartPos2 := i + 1;
break;
end;
if (StartPos2 > 0) and (EndPos > StartPos2) then
begin
result := result + Copy(mytext, StartPos2, EndPos - StartPos2);
delete (mytext, StartPos1, 1);
end
So I create the form and assign a logfile.
procedure TTagtextextract0r.FormCreate(Sender: TObject);
begin
Edit2.Text:=(TDirectory.GetCurrentDirectory);
AssignFile(LogFile, 'Wordlist.txt');
ReWrite(LogFile);
CloseFile(Logfile);
end;
To then get the files in question, I click a button which then reads them.
procedure TTagtextextract0r.Button3Click(Sender: TObject);
begin
try
sD := TDirectory.GetCurrentDirectory;
Files:= TDirectory.GetFiles(sD, '*.xml');
except
exit
end;
j:=Length(Files);
for k := 0 to j-1 do
begin
Listbox2.Items.Add(Files[k]);
sA:= TFile.ReadAllText(Files[k]);
iL:= Length(sA);
AssignFile(LogFile, 'Wordlist.txt');
Append(LogFile);
WriteLn(LogFile, (ExtractText('screen', sA)));
CloseFile (LogFile);
end;
end;
end.
My problem is that without the boolean loop in the function, the application only writes the one line per file and then stops but with the boolean code the application gets stuck in an infinite loop - but I can't quite see where the loop doesn't end. Is it perhaps that the "WriteLn" command can't then output the result of the function? If it can't, I don't know how to get a new line for every run of the function - what am I doing wrong here?
First you need to get a grip on debugging
Look at this post for a briefing on how to pause and debug a program gone wild.
Also read Setting and modifying breakpoints to learn how to use breakpoints. If you would have stepped through your code, you would soon have seen where you go wrong.
Then to your problem:
In older Delphi versions (up to Delphi XE2) you could use the PosEx() function (as suggested in comments), which would simplify the code in ExtractText() function significantly. From Delphi XE3 the System.Pos() function has been expanded with the same functionality as PosEx(), that is, a third parameter Offset: integer
Since you are on Delphi 10 Seattle you can use interchangeably either System.StrUtils.PosEx() or System.Pos().
System.StrUtils.PosEx
PosEx() returns the index of SubStr in S, beginning the search at
Offset
function PosEx(const SubStr, S: string; Offset: Integer = 1): Integer; inline; overload;
The implementation of ExtractText() could look like this (with PosEx()):
function ExtractText(const tag, text: string): string;
var
startPos, endPos: integer;
begin
result := '';
startPos := 1;
repeat
startPos := PosEx('<'+tag, text, startpos);
if startPos = 0 then exit;
startPos := PosEx('>', text, startPos)+1;
if startPos = 1 then exit;
endPos := PosEx('</'+tag+'>', text, startPos);
if endPos = 0 then exit;
result := result + Copy(text, startPos, endPos - startPos) + sLineBreak;
until false;
end;
I added sLineBreak (in unit System.Types) after each found text, otherwise it should work as you intended it (I believe).

Delphi : Operator not aplicable to this operand type

im a beginner to Aplication forms in Delphi, i need a little help please. So its basically a program that does Aritmetic count for numbers from
Memo box. I wanna also add interval to it. (-15;20> And i wanna do it for all ODD numbers.
Variables are listed here
soucet:SUM,
pocet:count of numbers,
Prumer:Arithmetic mean
procedure TForm1.Button3Click(Sender: TObject);
var soucet,prumer,x: Real;
i,pocet:Integer;
begin
Memo1.Clear;
soucet:=0;
pocet:=0;
i:=0;
While i<= Memo1.Lines.Count-1 do begin --
x:=StrToFloat (Memo1.lines[i]); --
If (x>-5) and (x<=5) then begin
soucet:= soucet + x;
inc(pocet);
end;
inc(i);
end;
If pocet>0 then begin
prumer:=soucet/pocet;
Memo1.Text:= floattostr(prumer);
end
else Memo1.Text:= 'Žádná čísla z intervalu (-15;20>';
But i only want this code to be for ODD numbers...
procedure TForm1.Button3Click(Sender: TObject);
var soucet,prumer,x: Real;
i,pocet:Integer;
begin
Memo1.clear;
soucet:=0;
pocet:=0;
i:=0;
While i<= Memo1.Lines.Count-1 do begin --
x:=StrToFloat (Memo1.lines[i]); --
If (x>-5) and (x<=5) then begin
If x mod 2<>0 then begin
soucet:= soucet + x;
inc(pocet);
end;
end;
inc(i);
end;
If pocet>0 then begin
prumer:=soucet/pocet;
Memo1.Text:= floattostr(prumer);
end
else Memo1.Text:= 'Žádná čísla z intervalu (-15;20>';
The problem is that it shows : Operator not aplicable to this operand type.
What should i do to remove this error ?
You have your xdeclared as real but the modoperator works on integer
Either
Declare x as integer and use StrToInt, TryStrToInt or StrToIntDef io StrToFloat
Truncate the real to an int like this: if Trunc(x) mod 2 <> 0 or even better use the built-in odd function like this: if odd(Trunc(x))
This will solve your immediate problem but you might want to read up on
input validation
clean code
and not related to your current code but important enough to mention
error/resource handling (try...finally)

Result value logic in Delphi?

I'm coding this function where if a string differs only by one character, returns the distinct characters position, if they're right the same is supposed to return -1 and -10 in the case they differ by more than 1 character.
Just for giving and example, '010' and '110' or '100' and '110' works good, returning 0 and 1 each...
However, when I try with '100' and '101'or with '110' and '111' I get a result of -1 when it should be 2! I've done the desktop testing and I can't just see the mistake.
function combine (m1, m2 : string) : integer;
var
dash : integer;
distinct : integer;
i : integer;
begin
distinct := 0;
dash := -1;
for i := 0 to Length(m1)-1 do
begin
if m1[i] <> m2[i] then
begin
distinct := distinct+1;
dash := i;
if distinct > 1 then
begin
result:= -10;
exit;
end;
end;
end;
result := dash;
end;
I'm always getting same length strings,
What am I doing wrong?
The main problem is that Delphi strings are 1-based. Your loop needs to run from 1 to Length(m1).
If you enabled range checking in the compiler options, then the compiler would have raised an error at runtime which would have led you to the fault. I cannot stress enough that you should enable range checking. It will lead to the compiler finding errors in your code.
Note also that this means that the returned values will also be 1-based. So an input of '100', '101' will give the result 3 since that is the index of the first difference.
You should also check that m1 and m2 are the same length. If not raise an exception.
Another tip. The idiomatic way to increment a variable by 1 is like so:
inc(distinct);
If you want to increment by a different value write:
inc(distinct, n);
So, I would write the function like this:
function combine(const m1, m2: string): integer;
var
i: integer;
dash: integer;
distinct: integer;
begin
if Length(m1)<>Length(m2) then begin
raise EAssertionFailed.Create('m1 and m2 must be the same length');
end;
distinct := 0;
dash := -1;
for i := 1 to Length(m1) do
begin
if m1[i] <> m2[i] then
begin
inc(distinct);
dash := i;
if distinct > 1 then
begin
result := -10;
exit;
end;
end;
end;
result := dash;
end;

Resources