0
votes

I try to convert some code from D2007 to XE5 and got

E2010 Incompatible types: 'AnsiChar' and 'Char'

from this code

  TSetOfChar = Set of Char;

  var
    CharacterSet: TSetOfChar;
    s: String;

    for j := 1 to Length(s) do
    begin
      Include(CharacterSet, s[j]);  // Error E2010
      if not CaseSensitive then
      begin
        Include(CharacterSet, AnsiUpperCase(s[j])[1]);  // Error E2010
        Include(CharacterSet, AnsiLowerCase(s[j])[1])   // Error E2010
      end
    end;

Any suggestion for solution ? I think it is enough to handle AnsiStrings.

2
Something similar: I've been having warning on everything like s[1] in ['A'..'Z','a'..'z','0'..'9'] in recent Delphi versions. Since I don't care about anything outside of the Ansi-range, I do AnsiChar(s[1]) in [etc.Stijn Sanders
Or use CharInSet(), like the compiler warning says.Remy Lebeau
@Stijn There are functions that test character properties. No need to shun international text.David Heffernan
sigh If you're interested, I've vented about this before here: plus.google.com/105759934778773466450/posts/LRqD794jd7qStijn Sanders

2 Answers

2
votes

The base type for a Delphi set can have at most 256 elements. Which means that your set is in fact really set of AnsiChar.

And your code is failing because AnsiUpperCase(s[j]) is of type string which is UnicodeString. Hence AnsiUpperCase(s[j])[1] is of type char which is WideChar. Which is what the compiler is trying to tell you. Despite their names, AnsiUpperCase and AnsiLowerCase operate on, and return UnicodeString.

If you really do still want to work with ANSI strings then you will need to use the AnsiUpperCase and AnsiLowerCase functions from the AnsiStrings unit. These versions operate on, and return AnsiString. Add that unit to your uses clause, and make sure you pass AnsiString values:

var
  AnsiStr: AnsiString;
....
AnsiStr := AnsiString(s);
for j := 1 to Length(AnsiStr) do
begin
  Include(CharacterSet, AnsiStr[j]); 
  if not CaseSensitive then
  begin
    Include(CharacterSet, AnsiUpperCase(AnsiStr[j])[1]);  
    Include(CharacterSet, AnsiLowerCase(AnsiStr[j])[1])   
  end
end;

Of course, it seems extremely inefficient to be creating strings containing single characters. Surely you can find a way to do this without using heap allocated strings and operating directly on characters.

An obvious improvement would be to call AnsiUpperCase once, passing the entire string, and iterate over the upper case string returned. Likewise for AnsiLowerCase. Or you could use CharUpperBuff and CharLowerBuff to avoid heap allocations.

And of course there's the whole issue of Unicode. You are using a Unicode Delphi but being confined to ANSI. Do you really want to do that? If you want to support Unicode then you have to stop using Delphi sets, I am afraid. You'll need a data type that can support sets of wide characters. You would also perhaps need to consider what to do with multi-byte UTF-16 characters.

2
votes
  uses
      ..., AnsiStrings;

  type
    TSetOfChar = Set of AnsiChar;

  var
    CharacterSet: TSetOfChar;
    s: AnsiString;

    for j := 1 to Length(s) do
    begin
      Include(CharacterSet, s[j])
      if not CaseSensitive then
      begin
        Include(CharacterSet, AnsiStrings.AnsiUpperCase(s[j])[1]);
        Include(CharacterSet, AnsiStrings.AnsiLowerCase(s[j])[1]);
      end
    end;

Alternatively:

  uses
      ..., AnsiStrings;

  type
    TSetOfChar = Set of AnsiChar;

  var
    CharacterSet: TSetOfChar;
    s, s2: AnsiString;

    if CaseSensitive then
    begin
      for j := 1 to Length(s) do
        Include(CharacterSet, s[j]);
    end else 
    begin
      s2 := AnsiStrings.AnsiUpperCase(s);
      for j := 1 to Length(s2) do
        Include(CharacterSet, s2[j]);
      s2 := AnsiStrings.AnsiLowerCase(s);
      for j := 1 to Length(s2) do
        Include(CharacterSet, s2[j]);
      end
    end;