From VBA to Delphi conversion (Optional arguments issue) - excel

At the moment I'm converting a project written in VBA to Delphi and have stumbled upon a problem with converting some Subs with Optional arguments.
Say, there is a Sub declaration (just an example, actual Subs have up to 10 optional parameters):
Sub SetMark
(x0 As Double, y0 As Double,
Optional TextOffset As Integer =5,
Optional TextBefore As String = "",
Optional Text As String = "",
Optional TextAfter As String = "mm",
Optional Color As String = "FFFFFF",
Optional ArrowPresent As Boolean = True)
That Sub subsequently can be called like this:
Call SetMark (15, 100,,,"135")
Call SetMark (100, 100, 8,, "My text here..", "")
'a lot of calls here
The Optional arguments are very flexible here, you can omit any of them, and you can assign a value to any of them as well. Unlike in Delphi.
Procedure SetMark
(x0: real; y0: real,
TextOffset: Integer =5;
TextBefore: ShortString = '';
Text: ShortString = '';
TextAfter: ShortString = 'mm';
Color: ShortString = 'FFFFFF';
ArrowPresent: Boolean = True);
It seems you cannot just make a copy of VBA call:
SetMark (15, 100,,,'135');// error here
So, the question is: is there any way to convert that Subs to Delphi procedures keeping the same flexibility in parameters?
My first idea was to use default parameters, but it doesn't work.
As for now it seems in Delphi I will have to pass all the parameters in the list with their values directly but that means a lot of work for reviewing and proper porting of VBA calls.
Any ideas?

Is there any way to convert the VBA subroutines to Delphi procedures, still keeping the same flexibility in parameters?
There is no way to achieve that – that flexibility to omit parameters, other than at the end of the list, simply does not exist.
For methods of automation objects, you can use named parameters, as described here: Named/optional parameters in Delphi? However, I very much recommend that you don't implement your classes as automation objects just to get that functionality.
Whenever you switch between languages, you will find differences that are inconvenient. That is inevitable. The best approach is to try to find the best way to solve the problem in the new language, rather than trying to force idioms from the old language into the new language.
In this case you might want to use overloaded functions or parameter objects as ways to alleviate this inconvenience.

Just to expand on the idea of refactoring to use a parameter object, you could declare a record like:
TSetMarkParams = record
x0 : double;
y0 : double;
TextOffset : integer;
TextBefore : string;
Text : string;
TextAfter : string;
Color : string;
ArrowPresent : boolean;
constructor Create(Ax0, Ay0 : double);
end;
And implement the constructor to populate default values as :
constructor TSetMarkParams.Create(Ax0, Ay0 : double);
begin
x0 := Ax0;
y0 := Ay0;
TextOffset := 5;
TextBefore := '';
Text := '';
TextAfter := 'mm';
Color := 'FFFFFF';
AllowPresent := true;
end;
Your procedure would then have signature :
procedure SetMark(ASetMarkParams : TSetMarkParams);
Which you could then, using your example of SetMark (15, 100,,,'135'); call as :
var
LSetMarkParams : TSetMarkParams
begin
LSetMarkParams := TSetMarkParams.Create(15, 100);
LSetMarkParams.Text := '135';
SetMark(LSetMarkParams);
end;
As collateral benefit, the above is much more readable as it saves you from going blind trying to count commas when returning to debug a troublesome method call.

Related

Codesys add elements to a derived struct

Is it possible to add elements to an instance of struct.
TYPE StructBase:
STRUCT
Start : INT;
Complete : INT;
END_STRUCT
END_TYPE
StructDerived : StructBase;
StructDerived.StateInit : INT;
StructDerived.StateMoveFwd : INT;
The elements of StructDerived are going to be numbered in an 'enum' function.
On completion the StructDerived elements are used for a case statement.
The idea is to have a complete abstract function ready with the basic functionalities i require. When this function is extended, it should be clear where what to add. so all units in the machine for example all have the same lay-out.
What i was looking into was something like this:
TYPE BaseState:
STRUCT
EnumVal : INT;
Name : STRING;
END_STRUCT
END_TYPE
TYPE StructBase:
STRUCT
StateArray : ARRY [..] OF BaseState;
END_STRUCT
END_TYPE
StructDerived : StructBase;
StructDerived[0].Name := 'StateInit';
StructDerived[1].Name := 'StateMoveFwd';
So if i would use it in a case:
CASE AbortingState OF
StructDerived[0].EnumVal:
....
The 'Name' is logged for state tracking. Looks like a lot of work where something like a list or dictionary would do the trick.
Thanks for any advice in this matter.
First, you cannot use strings in CASE. Only ANY_NUM or ANY_BYTE general types. So, if you want to use enumeration, you will use it like this.
TYPE EN_STEPS: (
enStateInit,
enStateMoveFwd
);
END_TYPE
TYPE BaseState:
STRUCT
EnumVal : INT;
Step : EN_STEPS;
END_STRUCT
END_TYPE
GLOBAL_VAR
StateArray : ARRAY [1..c_TOTAL] OF BaseState :=
(EnumVal := 0, Step := enStateInit),
(EnumVal := 0, Step := enStateMoveFwd);
END_VAR
GLOBAL_VAR CONSTANT
c_TOTAL: INT := 2;
END_VAR
Then in program
VAR
currentStep : EN_STEPS;
END_VAR
CASE currentStep OF
StateArray[0].Step:
//do something
END_CASE
But this is not exactly clear why you are doing this. Looks like there is not a practical application for that. You could simply do
TYPE EN_STEPS: (
enStateInit,
enStateMoveFwd
);
END_TYPE
VAR
currentStep : EN_STEPS;
END_VAR
CASE currentStep OF
enStateInit:
//do something
END_CASE
The same thing.
Second, if you want to make a universal function that accepts a different number of elements you must use pointers. This is a general answer, if you edit your question and describe what you want to make in general, I will edit my answer and extend it with a solution.
Structured Text is a strongly typed language and it is not possible to dynamically add elements to a struct at runtime.
A data structure is not a function,therefor you should design a Function Block instead of a Struct if you intend to have a
complete abstract function ready with the basic functionalities i
require
"case of" instructions have one conditional variable which is checked against one or many conditional instructions (integer literals).
You can't use variables as your conditional instructions.
You can use inheritance to extend Structs and Function Blocks in order to create a hierarchy of different modules and instatiate them in different occasions for different purposes.

Str in XE7 generates strange warning

Why does this code:
w: word;
s: String;
begin
str(w, s);
generate this warning in XE7:
[dcc32 Warning] Unit1.pas(76): W1057 Implicit string cast from 'ShortString' to 'string'
Tom
System.Str is an intrinsic function that dates from a byegone era. The documentation says this:
procedure Str(const X [: Width [:Decimals]]; var S: String);
....
Notes: However, on using this procedure, the compiler may issue a warning: W1057 Implicit string cast from '%s' to '%s' (Delphi).
If a string with a predefined minimum length is not needed, try using the IntToStr function instead.
Since this is an intrinsic, there is likely something extra going on. Behind the scenes, the intrinsic function is implemented by a call to an RTL support function that yields a ShortString. Compiler magic then turns that into a string. And warns you of the implicit conversion. The compiler magic transforms
Str(w, s);
into
s := _Str0Long(w);
Where _Str0Long is:
function _Str0Long(val: Longint): _ShortStr;
begin
Result := _StrLong(val, 0);
end;
Since _Str0Long returns a ShortString then the compiler has to generate code to perform the implicit converstion from ShortString to string when it assigns to your variable s. And of course it's then natural that you see W1057.
The bottom line is that Str only exists to retain compatibility with legacy Pascal ShortString code. New code should not be calling Str. You should do what the documentation says and call IntToStr:
s := IntToStr(w);
Or perhaps:
s := w.ToString;

Delphi: Executing Conditional Statements in a String

How can I execute in Delphi a conditional statements in a String?
In PHP there is something like this:
<?php
echo "Hello (isset($name) ? $name : 'Guest'));
?>
I'm assuming you actually want to evaluate code that is not known until runtime. That's the only reason why you would have code in a string. If my assumption is correct, then you cannot do that readily in Delphi. Delphi is compiled. So in order to execute Delphi code you need to compile it.
You could consider using a scripting language for this part of your program. There are many available.
Of course, if all you want is a conditional operator in Delphi then there is none built in but the RTL provides IfThen:
function IfThen(AValue: Boolean; const ATrue: string;
AFalse: string = ''): string;
Description
Conditionally returns one of two specified values.
IfThen checks the expression passed as AValue and returns ATrue if it evaluates to true, or AFalse if it evaluates to false. In Delphi, if the AFalse parameter is omitted, IfThen returns 0 or an empty string when AValue evaluates to False.
the closest thing you can get in Delphi is this :
Writeln('Hello ' + IIf(Name='', 'Guest', Name));
where IIf is defined as:
function iif(Test: boolean; TrueRes, FalseRes: string): string;
begin
if Test then
Result := TrueRes
else
Result := FalseRes;
end;
Please mind that this example only works with strings...
EDIT
As David suggested you can also use the IfThen function from the StrUtils unit
For a type independent IIF, use this:
function IIF(pResult: Boolean; pIfTrue: Variant; pIfFalse: Variant): Variant;
begin
if pResult then
Result := pIfTrue
else
Result := pIfFalse;
end;

Sorting multidimensional array in Delphi 2007

I have a multidimensional array similar to the one described below:
Matrix => array(
[0] => array( [0] => 7, [1] => 'hello' ),
[1] => array( [0] => 3, [1] => 'there' ),
[2] => array( [0] => 1, [1] => 'world' ),
)
What I'm trying to achieve is to sort this array using the values of Matrix[i][0]. What would be the most efficient way to do this? I looked into the recursive QuickSort function possibility, but it's rather complicated as I'm multidimensional arrays. There are many examples of quicksort, but none of them handle taking an "Array of Array of String" as an input.
Please ask for clarification if my text seems gibberish. I'm still fairly new to Delphi.
As Rob pointed out in his comment, there's no way to have an multi-dimensional array that stores both integers and strings (without resorting to variants).
If it's really just an array containing integer/string pairs, it would be much easier to store them in a TStringList using the Strings and Objects properties, and then use CustomSort to sort on the Object values:
program Project2;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
function MySortProc(List: TStringList; Index1, Index2: Integer): Integer;
begin
Result := Integer(List.Objects[Index1]) - Integer(List.Objects[Index2]);
end;
var
SL: TStringList;
i: Integer;
begin
SL := TStringList.Create;
try
for i := 0 to 10 do
SL.AddObject(Format('Item %d', [i]), TObject(Random(i)));
SL.CustomSort(#MySortProc);
for i := 0 to SL.Count - 1 do
WriteLn(Format('%d: %s', [Integer(SL.Objects[i]), SL[i]]));
ReadLn;
finally
SL.Free;
end;
end.
This produces
First of all I question whether or not you have chosen the correct type to represent your data structure. You say your type is
array of array of string
But it looks like the inner array always has exactly two elements, the first an integer, and the second a string. In that case the inner array should be replaced with a record.
type
TMyElement = record
ID: Integer;
Name: string;
end;
TMyArray = array of TMyElement;
And now you just have a one dimensional array. I assume you have no difficulty sorting one of those.
But suppose that you really did need a multi-dimensional array. Suppose that the array was ragged, i.e. that different inner arrays had different lengths. You can still view that array as a one-dimensional array. Declare it like this:
TStringArray = array of string;
TMyArray = array of TStringArray;
Now you can sort TMyArray just as if it were a one-dimensional array.
Note that I needed to declare a type for the inner array. The reason being that the sort function needs to be able to compare and exchange elements of the outer array. So you'll need functions to do that. And you need to define a type to achieve that. For example, your exchange function might look like this:
procedure Exchange(Index1, Index2: Integer);
var
temp: TStringArray;
begin
temp := MyArray[Index1];
MyArray[Index1] := MyArray[Index2];
MyArray[Index2] := temp;
end;
Without defining TStringArray, this would not be possible. That's because of the rather stringent assignment compatibility rules for dynamic arrays.
You can extend to as many dimensions as you like:
TString2DArray = array of TStringArray;
TMyArray = array of TString2DArray;
Again, you can use your standard array sort to sort this three dimensional version of TMyArray.

Pascal: Completely import a module into current scope

More Pascal woes.
Say I have 2 Units, MainUnit, and ExampleClass.
MainUnit:
Unit MainUnit;
interface
Uses ExampleClass;
function ReturnFive: Integer;
implementation
function ReturnFive: Integer;
begin
ReturnFive := 5;
end;
begin
end.
ExampleClass:
Unit ExampleClass;
{$mode objfpc}
interface
type
ClassThing = Class
SampleValue: Integer;
end;
implementation
begin
end.
Now, I'd like to only import MainUnit, but still be able to use ClassThing. MainUnit uses ExampleClass, but ClassThing isn't usable when you import MainUnit.
I don't really want to just use ExampleClass along with MainUnit, I'd prefer to keep it in one uses statement.
How do you do this?
put
type ClassThing = ExampleCLass.ClassThing;
in the interface of mainunit.
The principle also works for consts, but only "real" ones (not typed ones which are more initialized vars):
const myconst = unitname.myconst;
Nearly all my much used types are similar aliases, so that I can easily move around where they are defined without changing the uses clause in all the businesscode units

Resources