How to get the first element in a string? - delphi

I'm trying to figure out a way to check a string's first element if it's either a number or not.
if not(myString[0] in [0..9]) then //Do something
The problem is that I get an error "Element 0 inaccessible - use 'Length' or 'SetLength"
Another way came to my head from my C-like exprieince - convert the first element of the string to char and check the char,but there is no difference in the compile errors.
if not(char(myString[0]) in [0..9]) then //Do something
How do I accomplish it?

Strings are 1-based:
if not (myString[1] in ['0'..'9']) then // Do something

Pascal and Delphi indexes string from 1. This is a legacy from time where zero byte contained length, while next 255 (index 1 to 255) contained actual characters.
Joel Spolsky wrote quite good article on string issues:
http://www.joelonsoftware.com/articles/fog0000000319.html

Delphi strings use a 1-based index, so just rewrite to
if not(myString[1] in ['0'..'9']) then //Do something
Also take note of the quotes around the 0..9, otherwise you would be comparing characters to integers.

We should keep in mind some things:
String in Delphi is 0-based for mobile platforms and 1-based for Windows.
String in old versions of Delphi is AnsiString (1-byte per char) and WideString in new versions (2-bytes per char).
Delphi supports set of AnsiChar, but doesn't support set of WideChar.
So if we want to write a code compatible with all versions of Delphi, then it should be something like this:
if (myString[Low(myString)]>='0') and (myString[Low(myString)]<='9') then
// Do something

if not(myString[0] in [0..9]) then //Do something
If you're using Delphi 2009, the TCharacter class in Character.pas has functions like IsDigit to help simplify these kinds of operations.
Once you fix the indexing, of course. :)

With later updates to Delphi mobile code, the bottom string index changed from 0 to 1. When you compile older programmes, they compile and run correctly using 0 starting index. Programmes created with the later IDE produce an error. When you have mixtures, life gets complex!
It would be good to be able to take an older programme and tell the IDE that you want it brought up to date (maybe this would fix other things, like fonts getting scrambled when you answer a phone call!) but it would be good to get things consistent!

The simplest way to check to see if the first character of string is an integer, and then dispatch:
var
iResult : integer;
begin
if TryStrToInt( mySTring[1], iResult) then
begin
// handle number logic here iResult = number
end
else
begin
// handle non number logic here
end;
end;

I use a utility function to test the entire string:
function IsNumeric(const Value: string): Boolean;
var
i: Integer;
begin
Result := True;
for i := 1 to Length(Value) do
if not (Value[i] in ['0'..'9','.','+','-']) then
begin
Result := False;
Break;
end;
end;
The above code is for Delphi versions prior to 2007. In 2007 and 2009, you could change the integer variable i to a character c, and use for c in Value instead.
To test for integers only, remove the '.' from the set of characters to test against.

This is incorrect. ISO strings and older Pascal's also started at one. It is just a general convention, and afaik the s[0] thing is a result of that being vacant, and cheap to code in the UCSD bytecode interpreter. But that last bit is before my time, so only my guessing.
It results from the Pascal ability to have arbitrary upper and lower bounds, which provides for more typesafety accessing arrays.
Really old Pascal strings (till early eighties) strings were even worse than C ones btw. Multiple conventions were in used, but all were based on static arrays (like early C), but they were typically space padded, so you had scan back from the end till the spaces ended.
(removed the legacy tag, since being 1 based is not legacy. Accessing s[0] as length IS legacy, but that is not what the question is about)

Foreach element in strName
if not element in [0-9] then
do something
else
element is a digit
end if
Don't forget the quote between digits number.

Related

StrToFloat fails to report invalid floating point numbers in Delphi 64bits

The following code which attempts to convert a value well beyond the double precision range
StrToFloat('1e99999999')
correctly reports an incorrect floating point value in Delphi 10.2r3 with the Windows 32 bits compiler, but when compiled with the Window 64 bits compiler, it silently returns a 0 (zero).
Is there a way to have StrToFloat report an error when the floating point value is incorrect?
I have tried TArithmeticException.exOverflow, but this has no effect in that case.
I also tried TArithmeticException.exPrecision but it triggers in many usual approximation cases (f.i. it triggers when converting '1e9').
Issue was noticed with Delphi 10.2 update 3
addendum: to workaround the issue, I have started a clean-room alternative implementation of string to double conversion, initial version with tests can be found in dwscript commit 2ba1d4a
This is a defect that is present in all versions of Delphi that use the PUREPASCAL version of StrToFloat. That maps through to InternalTextToExtended which reads the exponent like this:
function ReadExponent: SmallInt;
var
LSign: SmallInt;
begin
LSign := ReadSign();
Result := 0;
while LCurrChar.IsDigit do
begin
Result := Result * 10;
Result := Result + Ord(LCurrChar) - Ord('0');
NextChar();
end;
if Result > CMaxExponent then
Result := CMaxExponent;
Result := Result * LSign;
end;
The problem is the location of
if Result > CMaxExponent then
This test is meant to be inside the loop, and in the asm x86 version of this code it is. As coded above, with the max exponent test outside the loop, the 16 bit signed integer result value is too small for your exponent of 99999999. As the exponent is read, the value in Result overflows, and becomes negative. So for your example it turns out that an exponent of -7937 is used rather than 99999999. Naturally this leads to a value of zero.
This is a clear bug and I have submitted a bug report: RSP-20333.
As for how to get around the problem, I'm not aware of another function in the Delphi RTL that performs this task. So I think you will need to do one of the following:
Roll your own StrToFloat.
Pre-process the string, and handle out of range exponents before they read StrToFloat.
Use one of the functions from the C runtime library that performs the same task.
Finally, I am grateful for you asking this question because I can see that my own program is affected by this defect and so I can now fix it!
Update:
You may also be interested to look at a related bug that I found when investigating: RSP-20334. It might surprise you to realise that, StrToFloat('߀'), when using the PUREPASCAL version of StrToFloat, returns 1936.0. The trick is that the character that is being passed to StrToFloat is a non-Latin digit, in this case U+07C0.

Delphi XE2 : #nn notation for extended Character

Please help me,
I know this may sound like very simple question, but i just can not figured it out how to make it work. I just started learning Unicode, so please give me some hint or example code.
I was converting my old encoding and decoding code from Delphi 5 to Delphi XE2. And when i call "Char" function it result in a different character, seem like it happen at the extended character of any encoding set.
At Delphi 5 :
Char(129) -> will result as empty char
At Delphi XE2 :
Char(129) -> will result #$81
I tried to used AnsiChar at delphi XE2, and the result is :
AnsiChar(129) -> will result as #129
What code should i used at delphi XE2, so it will return an empty char too. Not the #nn notation?
I need it to return the same result of Delphi 5, for the backward compatibility reason.
Is this have something to do with HIGHCHARUNICODE directive? I have read and tried it too, but still not luck.
Here the code that i tried at Delphi XE2, i make a simple one, but it did have a same logic with my encode / decode code. The code will get the char then put it into edit box.
procedure TForm1.Button1Click;
var
chars : Array[0..2] of AnsiChar;
ansi_string : AnsiString;
begin
chars[0] := AnsiChar(65);
chars[1] := AnsiChar(129);
chars[2] := AnsiChar(66);
ansi_string := chars;
// Here the ansi_string have a value of 'A'#$81'B'
EditBox1.Text := ansi_string;
// Here when i look the EditBox1.text in Evaluate/modify form,
// it shows 'A'#$0081'B'
// but at the form, it only show AB
end;
How can i make the ansi_string variable having a value of 'AB' instead of 'A'#$81'B'?
Thanks in Advance,
In Delphi 5, Char is AnsiChar, but in XE2 Char is WideChar instead. 127 is the highest signed value that an AnsiChar can hold, so a value of 129, which is hex $81, binary 10000001, would simply be interpreted as -127, which is also hex $81, binary 10000001. They are just different interpretations of the same bit value.
Depending on what your encoding/decoding code I actually doing, you will need to either use AnsiChar/AnsiString explicitly instead of Char/String generically, or switch to using Byte values, or else re-write the code to support Unicode properly and not make assumptions about the size of Char anymore. Hard to say since you did not show your actual code. But you should be OK with just using AnsiChar/AnsiString, since they do operate the same way they always have (the debugger may simply be displaying AnsiChar values differently, that's all).

Is there any way to get RTTI hints for a real48 and shortstring variable in a structure where FieldType is nil in TRttiField?

I have discovered what I think is an odd oversight (probably intentional) on the part of the Extended RTTI feature in Delphi.
I would like to dump all the fields in an record type that has about 1500 different fields in it. Yes, seriously.
Some of them are of type real48 and some are shortstring, for those two, it appears that FieldType is nil for these types at runtime:
function TRttiField.GetValue(Instance: Pointer): TValue;
var
ft: TRttiType;
begin
ft := FieldType;
if ft = nil then
raise InsufficientRtti; // This fires!
TValue.Make(PByte(Instance) + Offset, ft.Handle, Result);
end;
If I was willing to assume that all nil-fieldtype fields are in fact real48's, I could simply use the offset and (if the field width is 6) grab a real48 value.
However the second complication is that all shortstring (ie string[30]) types are similarly afflicted.
Has anybody got these two Ancient Pascal Types to work with modern Extended RTTI?
Right now I'm using a best-guess approach, and where that fails I am hardcoding rules by name of the field, but if there was some technique I could use that would get me there without having to write a lot of code to extract information from all these old pascal file-of-records that I am modernizing, I would appreciate a better idea.
Unfortunately Real48 does not have any type info.
You can see that when you try compile this:
program Project1;
begin
TypeInfo(Real48);
end.
The same goes for the string[n] syntax. But there you could probably fix it by defining your own string types like:
type
string30 = string[30];
That alone would not include the rtti for the record field so you need to hack/fix the rtti as I showed here: https://stackoverflow.com/a/12687747/587106

Delphi: how to automatically remove unused vars ("Variable 'x' is declared but never used" hint)

Is there any tool (preferably freeware) that can analyze Pascal/Delphi syntax and automatically remove unused vars?
In my case, I'm working with a very large Delphi code base, and the compiler hints report over one thousand cases of "Variable 'x' is declared but never used".
I would take hours to remove them by hand, and I might make errors, but the right tool should be able to do that safely and automatically.
I searched online but didn't find one... does anybody here know of such a tool?
Thanks...
Mark Brarford
I see your point and totally agree that such a tool would be useful when working with legacy code. Unfortunately I don't know of any existing tool (I should add freeware tool here, static analyis tools should of course be able to do it easily, but I don't know of any free static code analysis tool) that is capable doing that.
But I guess you could easily write such a tool in a few minutes. A small GUI with a memo and a button should be enough. Then just copy the compiler hints to the memo and press the button. The tool then parses every line. It can easily check if the line contains the hint you are looking for and each such line has the same structure, so parsing should be relatively easy. It can then extract the file name and the line number, open the file and remove the variable declaration. This can be a bit tricky in case of multiple variable declarations in one line but I think it is doable.
I don't know if that's too much effort for you compared to the task of removing all variable declarations yourself. But I would like to see such a tool, so feel free to write it :)
Hope that helped at least a bit.
Okay, I really can't see any problems here. For the parsing part:
function ParseHint (const HintText : String; out HintInfo : THintInfo) : Boolean;
var
I, J : Integer;
HintName : String;
begin
Result := False;
for I := 1 to Length (HintText) do
begin
if (HintText [I] = '(') then
begin
J := I + 1;
while (HintText [J] <> ')') do Inc (J);
HintInfo.LineNumber := StrToInt (MidStr (HintText, I+1, J-(I+1)));
HintInfo.SourceFile := MidStr (HintText, 12, I-12);
HintName := MidStr (HintText, J+3, 5);
if (HintName <> 'H2164') then Exit (False);
end;
if (HintText [I] = '''') then
begin
J := I + 1;
while (HintText [J] <> '''') do Inc (J);
HintInfo.VarName := MidStr (HintText, I+1, J-(I+1));
Exit (True);
end;
end;
end;
Well, reading the source file should be easy, so the only remaing part is removing the variable from its line of declaration. We can simply search for occurences of HintInfo.VarName in the line and check if the character before and after the occurence are no letters but only ' ', ',' or ':'. If this is the case we can just remove it. This covers all these cases:
var UnusedVar : Integer;
var
UnusedVar,
AnotherVar : Integer;
var
UnusedVar, AnotherVar : Integer;
Tell me if I'm wrong or if I forgot any cases but I think this would work and woulde solve the problem of removing unused variables from delphi source files using the compiler-generated hints.
The solution is simple, but requires those hours to be sure you don't make a mistake. First off, You can use Alt-F8 to step through each report one after the other (and Alt-F7 to step backwards). That makes locating them very easy. The cursor is put on the line for you. Then just press the '/' key twice to comment it out. Don't delete it, comment it. This way if you make a mistake you haven't lost any information. The presence of the variable and its data type is still recorded. You can tidy it up later at some point.
One caveat to all this: Conditional compilation may render some variables unused when built different ways. If this happens, then just uncomment the variable again, and put the condition around the declaration too.
Are you sure the variables shouldn't be used? I know the compiler figures out that they aren't used right now, but is that correct, perhaps many of these should be used, but a developer used x2 instead of x1 for instance, copy and paste?
While you might want to remove all those variables unscrutinized, I wouldn't be so hasty, they might be indications of bugs in your code that you'd like to fix.
Example:
procedure PlotPixelAtCenter(rect: Rectangle)
var
x, y: Integer;
begin
x := (rect.Left + rect.Right) div 2;
x := (rect.Top + rect.Bottom) div 2; // <-- bug here, should be y :=
PlotPixel(x, y);
end;
In this example you'll get an error about an unused variable, but this is a bug lurking. Of course, in this example the bug should be easy to find since the plotting will probably be off, but other similar bugs might be harder to spot.
If there is no such tool and you have some patience, I'm building a Delphi analysis and repair tool. And removal of unused symbols is on the list. It is a low proirity project so I can't give an estimate on when its ready.
Just to explain why this isn't a trivial task:
read the source
create a model that contains enough information for each symbol usage.
mark all unused symbols.
rewrite the source without the unneeded symbols.
Task 1 and 2 are hard (luckily for me those are already done). The Delphi language is quite complex. And you need all language elements to be able to recreate the source.
Task 3 is simple. Just flag all symbols that are unused. But beware of symbols in the interface section of a unit. They are possibly not used but needed later (or by some other project).
Task 4 depends.
Aproach A uses an intermediate format (for example a stringlist), you can then use the model to find the declaration of each unused symbol (bottom up else you possibly change the line numbers). You delete all not needed. And don't forget to delete the var keyword if it's the last var in the list!
Aproach B completely rewrites the source file. In this case, you must preserve all comments which is not really fun to do (but my model needs that too). You just removes the unused symbols from the model and rewrite it.
Always be sure to create a backup, because this can end up in disaster.

MD5 Hashing in Delphi 2009

In borland delphi 7 and even in delphi 2007 everything worked, but in delphi 2009 it just returns the wrong hash!
I use wcrypt2 script (http://pastebin.com/m2f015cfd)
Just have a look:
string : "123456"
hash:
Delphi 7 : "e10adc3949ba59abbe56e057f20f883e" - real hash.
Delphi 2007 : "e10adc3949ba59abbe56e057f20f883e" - real hash too.
And...
Delphi 2009 : "5fa285e1bebe0a6623e33afc04a1fbd5" - WTF??
I've tried a lot of md5 scripts, but delphi 2009 does the same with all of them. Any help? Thanks.
Your library is not Unicode aware. Just passing it an AnsiString won't be enough because it probably uses strings internally to store data.
You could try to update that library, wait for the author to update it, or just use the MessageDigest_5.pas that ships with Delphi 2009. It is in the source\Win32\soap\wsdlimporter folder, which you will need to either add to your path, or explicitly include it in your project.
Here is some sample code using it in Delphi 2009:
uses Types, MessageDigest_5;
procedure TForm16.Edit1Change(Sender: TObject);
var
MD5: IMD5;
begin
MD5 := GetMD5;
MD5.Init;
MD5.Update(TByteDynArray(RawByteString(Edit1.Text)), Length(Edit1.Text));
Edit2.Text := LowerCase(MD5.AsString);
end;
And you are in business:
MD5(123456) = e10adc3949ba59abbe56e057f20f883e
You could wrap it in a simple function call if you wanted to. It is important you cast to a RawByteString before casting to a TByteDynArray since the RawByteString cast drops all the extra Unicode characters. Granted if the edit contains Unicode characters then you could end up with bad data.
Keep in mind that GetMD5 is returning an interface, so it is reference counted, etc.
Merry Christmas!
Before someone can comment on hashing algorithms, it helps if they have at least a fundamental understanding of the underlying concepts and principles. All of the responses so far which have focused on endless typecasting are completely overkill, but even worse, will result in unreliable results if a unicode string is being hashed.
The first thing you need to understand is that hashing and encryption algorithms operate at the byte-level. That means they don't care what you're hashing or encrypting. You can hash integers, chars, plain ASCII, full unicode, bytes, longwords, etc etc. The algorithm doesn't care.
When working with strings, the ONLY thing you have to ensure is that the internal function of your hashing library returns an AnsiString in the function which spits out your resulting hash. That's it. That's all that matters.
Your actual code for YOUR project can (and should) be based on normal string input, which maps to unicodestring in Delphi 2009. You shouldn't be typecasting anything to ansistring or rawbytestring. By doing so, you immediately create a broken hash if and when the user tries to hash anything outside the scope of the ANSI character set. And in the world of hashing, a broken hash is both unreliable AND insecure.
Have you checked that your library has been correctly updated for D2009 and unicodification?
I kinda doubt the same code would do D7/D2007 and D2009 for this sort of things.
It is obvious that your lib is not unicode enabled.
Convert your string to AnsiString or RawByteString or UTF8String by declaring temp AnsiString and assign your uniode string to it.
Note that if you are using unicode specific chars that can't be translated to single codepage, you should convert your string to UTF8.
Then call MD5(PAnsiChar(YourTempString)).
Check that your lib may have PWideChar or UNICODE declarations, to skip this.
If you have wcrypt2.pas, use this function.
function md5ansi(const Input: AnsiString): AnsiString;
var
hCryptProvider: HCRYPTPROV;
hHash: HCRYPTHASH;
bHash: array[0..$7f] of Byte;
dwHashBytes: Cardinal;
pbContent: PByte;
i: Integer;
begin
dwHashBytes := 16;
pbContent := Pointer(PAnsiChar(Input));
Result := '';
if CryptAcquireContext(#hCryptProvider, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT or CRYPT_MACHINE_KEYSET) then
begin
if CryptCreateHash(hCryptProvider, CALG_MD5, 0, 0, #hHash) then
begin
if CryptHashData(hHash, pbContent, Length(Input) * sizeof(AnsiChar), 0) then
begin
if CryptGetHashParam(hHash, HP_HASHVAL, #bHash[0], #dwHashBytes, 0) then
begin
for i := 0 to dwHashBytes - 1 do
begin
Result := Result + AnsiString(Format('%.2x', [bHash[i]]));
end;
end;
end;
CryptDestroyHash(hHash);
end;
CryptReleaseContext(hCryptProvider, 0);
end;
Result := AnsiString(AnsiLowerCase(String(Result)));
end;
Are you perchance casting a generic string (which in Delphi 2009 is a UnicodeString) to a PAnsiChar and passing that into the hash function? That will not work. You first must cast the string into an AnsiString and then cast that one to PAnsiChar, a la:
PAnsiChar(AnsiString('123456'))
Also, try using RawByteString instead of AnsiString like dmajkic suggested. Avoid UTF8String since that's not an AnsiString and any characters outside the ASCII range (0..127) might get reinterpreted into multibyte characters.
In Jim's answer:
if we change
MD5.Update(TByteDynArray(RawByteString(Edit1.Text)), Length(Edit1.Text));
to
MD5.Update(TByteDynArray(RawByteString(Edit1.Text)), Length(RawByteString(Edit1.Text)));
will support better while Chinese characters exists.

Resources