The way adding strings and variants behaves in Delphi (10.2 Tokyo) was a complete surprise to me. Can someone provide a reasonable explanation for this "feature" or shall we call it a bug ?
function unexpected: string;
var v: Variant;
begin
result := '3';
v := 2;
result := v + result;
ShowMessage(result); //displays 5, I expected 23
result := '3';
v := 2;
result := result + '-' + v;
ShowMessage(result) //displays -1, I expected 3-2
end;
result := v + result
Delphi's Variant type is a slightly extended version of the Win32 API's VARIANT type and supposed to be compatible with it so long as you do not use any Delphi-specific types. Additionally, when you use Delphi-specific string types, it is supposed to behave like it would with the OLE string type. In the Win32 API, it is specifically documented that adding a string and a number will result in a (numeric) addition, not a string concatenation, that you need to have two string operands to get a string concatenation:
VarAdd:
Condition Result
Both expressions are strings Concatenated
[...]
One expression is numeric and the other a string Addition
[...]
I suspect VarAdd is defined like that to make things easier for VB users.
result := result + '-' + v
Here result + '-' should perform string concatenation since both operands are strings. '3-' + v is then treated as a numeric addition, requiring 3- to be parsed as a number. I believe that since there are contexts in which the sign follows the digits, this parse succeeds and produces -3. Adding 2 to that results in -1.
Related
Short Version
Given:
const
Whitespace = [$0009, $000A, $000C, $0020];
Works: if ch in Whitespace then...
Fails: case ch of Whitespace: ...
Long Version
Normally, if you were to have a case statement, you can include various values for each case, eg:
var
ch: UCS4Char; // unsigned 32-bit
case ch of
48..57: {asciiDigit}; //i.e. '0'..'9'
65..70: {asciiUpperHexDigit}; //i.e. 'A'..'F'
97..102: {asciiLowerHexDigit}; //i.e. 'a'..'f'
end;
That works, but I would like these values to be constants:
const
//https://infra.spec.whatwg.org/#code-points
asciiDigit = [Ord('0')..Ord('9')]; //https://infra.spec.whatwg.org/#ascii-digit
asciiUpperHexDigit = [Ord('A')..Ord('F')]; //https://infra.spec.whatwg.org/#ascii-upper-hex-digit
asciiLowerHexDigit = [Ord('a')..Ord('f')]; //https://infra.spec.whatwg.org/#ascii-lower-hex-digit
asciiHexDigit = asciiUpperHexDigit + asciiLowerHexDigit; //https://infra.spec.whatwg.org/#ascii-hex-digit
asciiUpperAlpha = [Ord('A')..Ord('Z')]; //https://infra.spec.whatwg.org/#ascii-upper-alpha
asciiLowerAlpha = [Ord('a')..Ord('z')]; //https://infra.spec.whatwg.org/#ascii-lower-alpha
asciiAlpha = asciiUpperAlpha + asciiLowerAlpha; //https://infra.spec.whatwg.org/#ascii-alpha
asciiAlphaNumeric = asciiDigit + asciiAlpha; //https://infra.spec.whatwg.org/#ascii-alphanumeric
Is there any arrangement of any syntax that will allow:
caseing a Cardinal
against a "set of Cardinals"?
Or am I permanently stuck with the following?
var
ch: UCS4Char; //unsigned 32-bit
case ch of
Ord('!'): FState := MarkupDeclarationOpenState;
Ord('/'): FState := EndTagOpenState;
Ord('?'): AddParseError('unexpected-question-mark-instead-of-tag-name');
UEOF: AddParseError('eof-before-tag-name parse error');
Whitespace: FState := SharkTankContosoGrobber;
else
if ch in asciiDigit then
begin
FReconsume := True;
FState := tsTagNameState;
end
else
AddParseError('invalid-first-character-of-tag-name parse error');
end;
Obviously, using the conceptual case matches the logic being performed; having to do if-elseif is...lesser.
Note: I don't need it to actually be a Delphi "set", that is a specific term with a specific meaning. I just want:
case ch of Whitespace: ...
to work the same way:
if ch in Whitespace then...
already does work.
And we know the compiler already is OK with comparing a Cardinal to a "set", because the following already works:
case ch of
$000A, $000D, $0009, $0032: ...
end;
It's comparing a Cardinal to a "set of numbers".
Bonus Reading
Delphi case statement for integer ranges
Efficiently compare an integer against a static list of integers in Delphi?
any way to compare an integer variable to a list of integers in if statement
What Delphi type for 'set of integer'?
No, this is not supported.
According to the official documentation:
A case statement has the form:
case selectorExpression of
caseList1: statement1;
...
caseListn: statementn;
end
where [...] each caseList is one of the following:
A numeral, declared constant, or other expression that the compiler can evaluate without executing your program. It must be of an ordinal type compatible with selectorExpression. [...]
A subrange having the form First..Last, where First and Last both satisfy the criterion above and First is less than or equal to Last.
A list having the form item1, ..., itemn, where each item satisfies one of the criteria above.
Hence, this only allows single values, explicit ranges, and lists of such values, as part of the case syntax.
Although the Delphi documentation is good, it isn't perfect and you cannot rely on it to 100%. However, I'm sure all experienced Delphi developers will agree that a caseList cannot be a predeclared single-identifier "collection" of ordinal values compatible with selectorExpression.
You may file a feature request at Embarcadero's Jira.
But you can use the range syntax and a previously declared subrange type (not set constant) to achieve something partly similar:
type
TAsciiDigit = '0'..'9';
TAsciiLatinCapitalLetter = 'A'..'Z';
TAsciiLatinSmallLetter = 'a'..'z';
procedure TForm1.FormCreate(Sender: TObject);
begin
var c := 'R';
case c of
Low(TAsciiDigit) .. High(TAsciiDigit):
ShowMessage('Digit!');
Low(TAsciiLatinCapitalLetter) .. High(TAsciiLatinCapitalLetter):
ShowMessage('Capital!');
Low(TAsciiLatinSmallLetter) .. High(TAsciiLatinSmallLetter):
ShowMessage('Small letter!');
else
ShowMessage('Something else.');
end;
end;
Bonus remark: In fact, the non-100% accuracy of the documentation can be seen in the section quoted above:
selectorExpression is any expression of an ordinal type smaller than 32 bits
That's nonsense. selectorExpression certainly can be a 32-bit integer.
procedure TForm1.Button1Click(Sender: TObject);
var
xlap,xlbook,xlsheet:variant;
y:integer;
begin
xlap:=createoleobject('excel.application');
xlbook:=xlap.workbooks.add;
xlap.visible:=true;
xlsheet:=xlbook.worksheets.add;
for y:=1 to 50 do
begin
xlsheet.cells[y,2].value:= concat('=IF(A',y,'>6,"Good","Bad")')
inc(y);
end;
end;
That's my code to make an Excel through Delphi. I want to input the IF formula into column B of the Excel, but there is error when I use the concat('=IF(A',y,'>6,"Good","Bad")').
May be I need another way to include y between those strings.
Any suggestion? Thanks before
Delphi has a format statement bit like sprintf printf in c, well nearly
xlsheet.cells[y,2].value:= format('=IF(A%d>6,"Good", "Bad")',[y])
%d is a place holder for an int. Look it up for loads of other stuff.
NB the variables you want to interpolate are assed in an array.
In addition to Tony's answer about Format, here are a couple of other approaches. (Format is great if you have mixed types or many values, but it carries some overhead that might not be needed.)
You can concatenate strings with a simple + - in fact, the Concat documentation says it does the same thing but is faster:
Temp := 'ing';
Str := 'Test' + Temp; // Str = 'Testing'
As your y variable is an integer, you'll need to convert it to a string first (note you don't need to Inc(y);, as the loop will do that already - it's automatically incremented from the starting value to the ending value on each pass through the loop):
for y:=1 to 50 do
begin
xlsheet.cells[y,2].value:= '=IF(A' + IntToStr(y) + '>6,"Good","Bad")';
end;
The erro msg "Type mismatch in expression" appear when I run this code:
CDSIndicados.Filtered := False;
CDSIndicados.Filter := 'EDICOES_ID like ' + QuotedStr(IntToStr(Integer(cxComboBox1.Properties.Items.Objects[cxComboBox1.ItemIndex])));
CDSIndicados.Filtered := True;
I know this message may appear when there is an error in the data type of the field. But I could not fix. Was that it?
I'm suspecting that your EDICOES_ID field is an integer value, in which case you don't need to quote it in your filter expression and the LIKE operator isn't supported AFAIK. If it is a string field, you do need the quotes and LIKE is supported, but you typically want a wildcard in the expression as well. (LIKE is only supported for character (string) type fields. For numerics or dates, you need to use the usual comparison operators >, <, >=, <=, = or BETWEEN.)
Do yourself a favor, too, and declare a local variable, and making sure that there's actually an item selected in the ComboBox before trying to access its Objects. I've added one both for the ItemIndex and for intermediate storage of the typecast Object you're retrieving, which makes it much easier to debug if you need to do so.
Here's a solution either way (whether it's an integer field, or a string that needs quoting).
var
Idx, Value: Integer;
begin
Idx := ComboBox1.ItemIndex;
if Idx > -1 then
begin
CDSIndicados.Filtered := False;
Value := Integer(cxComboBox1.Properties.Items.Objects[Idx]);
// If the field is an integer, you don't need a quoted value,
// and LIKE isn't supported in the filter.
CDSIndicados.Filter := 'EDICOES_ID = ' + IntToStr(Value);
// Not relevant here, but LIKE isn't supported for date values
// either. For those, use something like this
CDSIndicados.Filter := 'EDICOES_DATE = ' + QuotedStr(DateToStr(Value));
// or, if the field is string and you want LIKE, you need to
// quote the value and include a wildcard inside that quoted
// string.
CDSIndicados.Filter := 'EDICOES_ID LIKE ' + QuotedStr(IntToStr(Value) + '%');
CDSIndicados.Filtered := True;
end;
end;
I'm doing some work with code generation, and one of the things I need to do is create a function call where one of the parameters is a function call, like so:
result := Func1(x, y, Func2(a, b, c));
TStringList.CommaText is very useful for generating the parameter lists, but when I traverse the tree to build the outer function call, what I end up with looks like this:
result := Func1(x, y, "Func2(a, b, c)");
It's quoting the third argument because it contains commas, and that produced invalid code. But I can't do something simplistic like StringReplace all double quotes with empty strings, because it's quite possible that a function argument could be a string with double quotes inside. Is there any way to make it just not escape the lines that contain commas?
You could set QuoteChar to be a space, and you'd merely get some extra spaces in the output, which is generally OK since generated code isn't usually expected to look pretty. String literals would be affected, though; they would have extra spaces inserted, changing the value of the string.
Free Pascal's TStrings class uses StrictDelimiter to control whether quoting occurs when reading the DelimitedText property. When it's true, quoting does not occur at all. Perhaps Delphi treats that property the same way.
Build an array of "unlikely characters" : non-keyable like †, ‡ or even non-printable like #129, #141, #143, #144.
Verify you don't have the 1st unlikely anywhere in your StringList.CommaText. Or move to the next unlikely until you get one not used in your StringList.CommaText. (Assert that you find one)
Use this unlikely char as the QuoteChar for your StringList
Get StringList.DelimitedText. You'll get the QuoteChar around the function parameters like: result := Func1(x, y, †Func2(a, b, c)†);
Replace the unlikely QuoteChar (here †) by empty strings...
What about using the Unicode version of AnsiExtractQuotedStr to remove the quotes?
Write your own method to export the contents of your TStringList to a string.
function MyStringListToString(const AStrings: TStrings): string;
var
i: Integer;
begin
Result := '';
if AStrings.Count = 0 then
Exit;
Result := AStrings[0];
for i := 1 to AStrings.Count - 1 do
Result := Result + ',' + AStrings[i];
end;
Too obvious? :-)
Alternatively, what would happen if you set StringList.QuoteChar to #0 and then called StringList.DelimitedText?
We have written a descendant class of TStringList in which reimplemented the DelimitedText property. You can copy most of the code from the original implementation.
var
LList: TStringList;
s, LOutput: string;
begin
LList := TStringList.Create;
try
LList.Add('x');
LList.Add('y');
LList.Add('Func2(a, b, c)');
for s in LList do
LOutput := LOutput + s + ', ';
SetLength(LOutput, Length(LOutput) - 2);
m1.AddLine('result := Func1(' + LOutput + ')');
finally
LList.Free;
end;
end;
Had the same problem, here's how I fixed it:
s := Trim(StringList.Text)
that's all ;-)
I'm working with Delphi 2009,I binged my question,but the answers I've gotten are outdated since It doesn't recognise StrtoFloat in Delphi2009.
I'm asking how to convert an integer ,for example, '1900000' to '1,900,000'?
You can also use the format command. Because the format expects a real number, adding 0.0 to the integer effectively turns it into an extended type.
Result := Format('%.0m',[intValue + 0.0]));
This handles negative numbers properly and adds the currency symbol for the users locale. If the currency symbol is not wanted, then set CurrencyString := ''; before the call, and restore it afterwards.
SavedCurrency := CurrencyString;
try
CurrencyString := '';
Result := Format('%.0m',[intValue + 0.0]));
finally
CurrencyString := SavedCurrency;
end;
To force commas, just set the ThousandSeparator := ',';
CurrencyString := '!';
ThousandSeparator := '*';
Result := Format('%.0m',[-1900000.0]);
// Returns (!1*900*000) in my locale.
The "period" in the mask determines how the fractional portion of the float will display. Since I passed 0 afterwards, it is telling the format command to not include any fractional pieces. a format command of Format('%.3m',[4.0]) would return $4.000.
I currently use this :
function FloatToCurrency(const f: double): string;
begin
Result := FormatFloat('#,###.##;1;0', f);
end;
It doesn't work with negative numbers, but since you need currency you won't have that problem.
You can assign Integer to Currency directly by assignment, the compiler will do the conversion for you:
var
Int : Integer;
Cur : Currency;
begin
Int := 1900000;
Cur := Int;
ShowMessage(CurrToStr(Cur)); // 1900000
ShowMessage(Format('%m', [Cur]); // 1,900,000.00 in US/UK/NZ/AU etc, "1 900 000,00" in Spain etc.
ShowMessage(Format('%.0m', [Cur]); // 1,900,000 in US/UK/NZ/AU etc, "1 900 000" in Spain etc.
end;
If you want Commas using Spanish regional settings set ThousandSeparator := ','; or use the extended CurrToStrF(amount, ffCurrency, decimals, FormatSettings)) version.
The verison with FormatSettings is also thread-safe.
Note: You can't assign Currency to Integer directly, You would need to use Int := Trunc(Cur) but this is inefficient as it converts to float first (unless compiler does something smart).
wouldnt this be more of a format thing, delphi should have some type of support for formating the number into a string the way you want right? Besides isnt the newer versions of delphi more aligned with the .net framework?