Loop through irregular enumeration in Delphi - delphi

1) Does anyone know if it is possible to loop through an irregular enumeration in Delphi (XE)?
Looping over a normal enumeration is ok. From Delphi Basics:
var
suit : (Hearts, Clubs, Diamonds, Spades);
begin
// Loop 3 times
For suit := Hearts to Diamonds do
ShowMessage('Suit = '+IntToStr(Ord(suit)));
end;
But, if 'suit' instead is declared as
var
suit : (Hearts=1, Clubs, Diamonds=10, Spades);
it loops 10 times. Not suprising, but I would like to loop 3. The only solution I've found so far is converting an enumeration to a set and use the 'for ... in'-loop like on delphi.about.com.
So, if answer to question 1) is no, then:
2) How to convert from enumeration to set in Delphi?
The context I am using it in is a component array of edit-boxes (TEdit) that has an irregular numbering (edit1, edit5, edit7, edit3, ...). While it is possible to reorder all the edit-boxes, it removes the reason of using enumeration as a flexible way to allow addition of an edit-box in the middle of the enumeration.

I do not have a Delphi compiler at hand right now, but I tink that gabr's approach can be rather significantly improved by doing
type
TSuit = (Hearts = 1, Clubs, Diamonds = 10, Spades);
const
Suits: array[0..3] of TSuit = (Hearts, Clubs, Diamonds, Spades);
Who knows, maybe it doesn't even compile.

type
TSuit = (Hearts=1, Clubs, Diamonds=10, Spades);
var
suit: TSuit;
suitEnum: array [1..4] of TSuit;
//initialization
suitEnum[1] := Hearts;
suitEnum[2] := Clubs;
suitEnum[3] := Diamonds;
suitEnum[4] := Spades;
for suit in suitEnum do
DoSomething(suit);

I always use
var
s: TSuit;
begin
for s := Low(TSuit) to High(TSuit) do
{something};
end;

Loop using Ord(Hearts) to Ord(Spades) ?

A dirty option, useful for small enumerations:
type
TSuit = (Hearts = 1, Clubs, Diamonds = 10, Spades);
var
Suit: TSuit;
begin
for Suit in [Hearts, Clubs, Diamonds] do
WriteLn(Ord(Suit));
Works nice in Delphi 2007. Don't know about older versions. Be aware, using for Suit in [Hearts..Diamonds] do has the same problem as your loop.Btw, I use WriteLn() because I tested this in a console application. :-)

It should be understood (and often isn't) that the moment you put hard ordinal assignments into an enumeration, it ceases for all intents to be a Pascalian enumerated type - it just becomes a "bag of constants", which is not the same thing. This is what C-programmers call enumerations. However, a Pascalian enumerated type is ORDINAL in all criterion: It has discrete consecutive values that respond meaningfully to the base operations ORD, PRED, SUCC. Enumerations in C don't do this, and neither do enums in Pascal once you force the ordinals apart.
THIS is the reason that Delphi's RTTI basically refuses to return type information once this has been done. To all intents the type is essentially a tkUnknown, and has to be treated as a 'bag' of compile-time constants. It is only because it still plays lip service to being an ordinal and has (sometimes shaky) support for use in sets that people are led into believing it should still behave like a proper enumerated type. It's better to just understand it for what it really is: a nod to enumerated values in C. Avoid mixing the coding metaphor!
If you do this, then your solution becomes obvious: you use an enumerated type (a proper one) to index a corresponding array of CONSTANTS. Then you can make the ordinals whatever you want, and the enums retain their full RTTI definitions as a proper enumeration. So: your ENUMERATED TYPE contains proper unchanged ordinal values. You get your funny numbers by indexing the constants array using the enumeration -ergo array [MyEnums] of byte = (1,3,8,22,99,whatever)

Related

Error compiling HtmlViewer component for Delphi 7

I am trying the compile the HtmlViewer component for Delphi 7 (https://github.com/BerndGabriel/HtmlViewer). Opened the project Frameviewer7.dpk under package subdir
However I am getting the following compilation errors:
HtmlBuffer.pas(1611): Array Type required.
Which corresponds to the following code:
if FStart.BytePtr[0] = 0
And FStart is defined as FStart: TBuffPointer;
TBuffPointer = record
case Integer of
0: (BytePtr: PByte;);
1: (WordPtr: PWord;);
2: (AnsiChr: PAnsiChar;);
3: (WideChr: PWideChar;);
end;
Not sure what is wrong here. My compiler is Delphi7
FStart.BytePtr[0] indicates that FStart.BytePtr is an array, and the value of this expression is the first (0th) element in this array.
However, FStart.BytePtr is actually a pointer. But often you can use arrays and pointers to achieve the same task -- either you use an array of TSomeType, or you use a pointer to the first element in an in-memory list of TSomeType items.
I assume this is what is going on here. Hence, you want to get the first item of a list of byte values, the first occurring at address FStart.BytePtr. To obtain the byte at this location, you dereference the pointer using ^: FStart.BytePtr^.
The code you have found tries to access data using array notation on a pointer. This syntactic sugar might work in some newer version or Delphi, or using some compiler option. (I don't recall.)
This syntax uses a feature of later Delphi compilers that allows you to use indexed references for offsets from typed pointers. In some versions of Delphi this requires the POINTERMATH compiler option or directive to be specified.
Unfortunately this is not supported in Delphi 7.
The typical way to work around this is to use an array type and declare a pointer to this array type. The actual bounds of the array type are not important (in the sense that you will be using the pointer type so you will not be creating actual large array structures, only treating pointers as if they were references to such structures).
The only consideration is that the upper limit needs to be higher than or equal to the highest index you will require to specify in order to keep the compiler happy that any literal indexes you specify may be valid.
i.e. if you specified an array of only 100 items then any code that attempted to reference a 101st item would fail bounds checking either at compile time or runtime (if runtime checks are enabled).
So for a simple example we'll use an array of 65535 items:
const
MAX_BYTEARRAYDIM = 65535;
type
TByteArray = array[0..MAX_BYTEARRAYDIM] of Byte;
PByteArray = ^TByteArray;
procedure SomeExampleMethod;
var
pb: PByteArray;
begin
// ..
pb[12] := 25; // The array type is 0 based so this sets the value of byte offset 12 bytes from the address in pb
end;
This has the advantage (should it be a concern in your code) of being portable to all versions of Delphi.
Application in Your Case
In your specific case you could redefined the BytePtr type in this way. Not being familiar with the HTMLViewer code I cannot say whether this may be practical.
An alternative would be to declare the necessary array and pointer types and to to typecast as and where required, e.g.:
if PByteArray(FStart.BytePtr)[0] = 0
Of course, the same technique can be applied to other pointer types as required.

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

why two aliases to "array of string" treated differently?

In Pascal there are two kinds of type declarations:
type aliases: type NewName = OldType
type creation: type NewType = type OldType
The former is just creating convenient shorthand, like typedef in C. The aliases are compatible one to another and to their original type. The created types are intentionally incompatible and cannot be mixed without explicit and unsafe by definition typecast.
var
nn: NewName; nt: NewType; ot: OldType;
...
nn := ot; // should work
nt := ot; // should break with type safety violation error.
nt := NewType(ot); // Disabling type safety. Should work even if
// it has no sense semantically and types really ARE incompatible.
Those are Pascal basics as i understand them.
Now let's look at one certain type and two its aliases:
System.Types.TStringDynArray = array of string;
System.TArray<T> = array of T;
in particular that means TArray<string> = array of string; by definition.
Now let's take function returning the former type alias and feed its result to the function expecting the latter one:
uses Classes, IOUtils;
TStringList.Create.AddStrings(
TDirectory.GetFiles('c:\', '*.dll') );
TStringList.Create.AddStrings(
TArray<string>( // this is required by compiler - but why ???
TDirectory.GetFiles('c:\', '*.dll') ) );
1st snippet would not compile due to types violation.
2nd one happily compiles and works, but is fragile towards future type changes and is redundant.
QC tells that compiler is right and the RTL design is wrong.
http://qc.embarcadero.com/wc/qcmain.aspx?d=106246
WHY compiler is right here ?
Why those aliases are incompatible ?
Even the very manner RTL was designed suggests that they were deemed compatible!
PS. David suggested even simplier example, without using TArray<T>
type T1 = array of string; T2 = array of string;
procedure TForm1.FormCreate(Sender: TObject);
function Generator: T1;
begin Result := T1.Create('xxx', 'yyy', 'zzz'); end;
procedure Consumer (const data: T2);
begin
with TStringList.Create do
try
AddStrings(data);
Self.Caption := CommaText;
finally
Free;
end;
end;
begin
Consumer(Generator);
end;
Same gotcha without explanation...
PPS. There are a number of doc refs now. I want to stress one thing: while this restriction might be indirectly inherited from Pascal Report of 1949, today is 2012 and Delphi used very differently from school labs of half-century ago.
I named few BAD effects of keeping this restrictions, and yet did not saw any good one.
Ironic thing, that this restricion may be lifted without breaking rules of Pascal: in Pascal there is no such non-strict beast as Open Arrays and Dynamic Arrays. So let those original fixed arrays be restricted as they wish, but Open Arrays and Dynamic Arrays are not Pascal citizens and are not obliged to be limited by its codebook!
Please, communicate Emba in QC or maybe even here, but if u just pass by without expressing your opinion - nothing would change!
The key to understanding this issue is the Type Compatibility and Identity topic in the language guide. I suggest you have a good read of that topic.
It is also helpful to simplify the example. The inclusion of generics in the example serves mainly to complicate and confuse matters.
program TypeCompatibilityAndIdentity;
{$APPTYPE CONSOLE}
type
TInteger1 = Integer;
TInteger2 = Integer;
TArray1 = array of Integer;
TArray2 = array of Integer;
TArray3 = TArray1;
var
Integer1: TInteger1;
Integer2: TInteger2;
Array1: TArray1;
Array2: TArray2;
Array3: TArray3;
begin
Integer1 := Integer2; // no error here
Array1 := Array2; // E2010 Incompatible types: 'TArray1' and 'TArray2'
Array1 := Array3; // no error here
end.
From the documentation:
When one type identifier is declared using another type identifier, without qualification, they denote the same type.
This means that TInteger1 and TInteger2 are the same type, and are indeed the same type as Integer.
A little further on in the documentation is this:
Language constructions that function as type names denote a different type each time they occur.
The declarations of TArray1 and TArray2 fall into this category. And that means that these two identifiers denote different types.
Now we need to look at the section discussing compatibility. This gives a set of rules to follow to determine whether or not two types are compatible or assignment compatible. We can in fact shortcut that discussion by referring to another help topic: Structured Types, Array Types and Assignments which states clearly:
Arrays are assignment-compatible only if they are of the same type.
This makes it clear why the assignment Array1 := Array2 results in a compiler error.
Your code looked at passing parameters, but mine focused on assignment. The issues are the same because, as the Calling Procedures and Functions help topic explains:
When calling a routine, remember that:
expressions used to pass typed const and value parameters must be assignment-compatible with the corresponding formal parameters.
.......
Delphi is a strongly typed language. That means that identical (in this case I mean their definitions look exactly the same) types are not assignment compatible.
When you write array of <type> you are defining a type and not an alias. As David already said in his comment the two identical types like
type
T1 = array of string;
T2 = array of string;
are not assignment compatible.
Same goes for
type
TStringDynArray = array of string;
TArray<T> = array of string;
Often people forget about the incompatibility of identical types and my guess would be that they did when they introduced IOUtils for example. Theoretically the definition of TStringDynArray should have been changed to TStringDynArray = TArray<string> but I guess that could have raised other problems (not saying bugs with generics...).
I also had the same problem with Delphi, where I wanted to pass values from one identical array to another. Not only did I have "incompatibility" problems with two like array assignments, but I also could not use the "Copy()" procedure. To get around this problem, I found that I could use a pointer to an type array of array of string, instead.
For example:
type RecArry = array of array of string
end;
var TArryPtr : ^RecArry;
Now, I can pass the values from any fixed array to another identical array without any compatibility or function problems. For example:
TArryPtr := #RecArry.LstArray //This works!
TArryPtr := #LstArray //This also works!
With this created array assignment template, I can now work with all two dimensional arrays without any problems. However, it should be understood, that when accessing this type of string array pointer, an extra element is created, so that when we would expect this type of array 2D array below, for example:
Two_Dimensional_Fixed_Array[10][0]
We now get an extra element adjusted array as seen here:
New_Two_Dimensional_Fixed_Array[10][1]
This means that we have to use some slightly tricky code to access the pointer array, because all the populated elements in Two_Dimensional_Fixed_Array[10][0] have moved down, so that they are offset by 1, as in New_Two_Dimensional_Fixed_Array[10][1].
Therefore where we would normally find the value 'X' in Two_Dimensional_Fixed_Array[1][0], it will now be found here in TArryPtr[0][1].
Its a trade off we all have to live with!
Another important note to bear in mind is the definition of a pointer array when it is declared. When a pointer array is type declared, the Borland compiler will not allow the Pointer array to have the same element size as the array to which it is pointing too. For example, if an array is declared as:
Orig_Arry : array [1..50,1] of string;
The pointer array which should point to it would be declared in the following fashion:
Type Pntr_Arry : array [1..50,2] of string;
Did you notice the the extra element? I am guessing the the Borland compiler has to widen the array pointer to allow for the pointer address.

RTTI properties not returned for fixed enumerations: is it a bug?

I need to browse all published properties of some classes.
Properties where the type is an enumeration with fixed values are not listed.
See example below:
TMyEnum = (meBlue, meRed, meGreen);
TMyEnumWithVals = (mevBlue=1, mevRed=2, mevGreen=3);
TMyClass =
...
published
property Color: TMyEnum read FColor write SetColor; // This one is found
property ColorVal: TMyEnumWithVals read FColorVal write SetColorVal; // This one is never found
end;
I need fixed values because these properties are stored in a database and I need to ensure that allocated values will always be the same, regardless of Delphi compiler choices in next versions, and prevent any misplaced insert of future values in the enumeration list.
I tried with both new Delphi 2010 RTTI (with .GetDeclaredProperties) and "old" RTTI (with GetPropInfos): all properties are found except the above type of property.
The same behaviour is seen on all classes. I also reproduced this in a sample project.
Tried with and without various RTTI directives without change.
Is this a bug, a known limitation?
Is there a workaround (except removing fixed values of the enumeration)?
Using Delphi2010 Ent+Update5
[Edit] The answer below provides the workaround: The first value of the enumeration must be set to 0 instead of 1, and values contiguous. Tested and working solution.
Thanks,
Barry Kelly says:
Discontiguous enumerations and enumerations which don't start at zero don't have typeinfo. For typeinfo to be implemented, it would need to be in a different format from the existing tkEnumeration, owing to backward compatibility issues.
I considered implementing a tkDiscontiguousEnumeration (or possibly better named member) for Delphi 2010, but the benefit seemed small considering their relative scarcity and the difficulties in enumeration - how do you encode the ranges efficiently? Some encodings are better for some scenarios, worse for others.
So it's not a bug, but a known limitation.
This is a limitation. Enumerated types with explicit ordinality can not have RTTI. Documented here.
Possible solution is to declare Reserved values within your enumerated type, eg:
TMyEnum = (meReserved1, meBlue, meRed, meGreen, meReserved2, meWhite); // and so on
...
Assert(MyEnum in [meBlue..meGreen, meWhite], 'sanity check failed')
Practical enums tends to occupy contiguous ranges, so you probably will not end up in having declaring gazillions of reserved values. In case you are interoperating with C style bitfields you will have to split value to distinct enums and sets.
To add some more background information, Enums with values, are treated as subrange types with predefined constants.
For example:
type
TMyEnumWithVals = (mevBlue=1, mevRed=2, mevGreen=3);
Is treated as:
type
TMyEnumWithVals = 1..3;
const
mevBlue : TMyEnumWithVals = 1;
mevRed : TMyEnumWithVals = 2;
mevGreen : TMyEnumWithVals = 3;
However, you can't combine these with integers without casting.
var
enum : TMyEnumWithVals;
ival : Integer;
begin
enum := mevBlue;
ival := Integer(mevRed);
To make matters worse, if you use:
type
TTypeId = (tidPrimary = 100, tidSecundary = 200, tidTertiary = 300);
TTypeArray = array [TTypeID] of string;
You get an array of 201 elements, not of 3. You can use the values in between using:
var
enum : TTypeId ;
ival : Integer;
begin
enum := TTypeId(150); // Is valid.
This information can be found in the Delphi language guide, with the enum type section.

How to get the first element in a string?

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.

Resources