Pascal: Completely import a module into current scope - 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

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.

From VBA to Delphi conversion (Optional arguments issue)

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.

TThreadList and "with" statement

just a short question regarding how to use TThreadList. Is it safe to use it with "with" statement as follows:
with FEngineList.DAQEngines.LockList do
begin
try
for Idx := 0 to Count - 1 do
Items[idx].Param1 := cos(2*pi*I/Count);
...
...
finally
FEngineList.DAQEngines.UnlockList;
end;
end;
or should I explicitly do it like here:
uEngines := FEngineList.DAQEngines.LockList;
try
with uEngines do
begin
for Idx := 0 to Count - 1 do
Items[idx].Param1 := cos(2*pi*I/Count);
...
...
end;
finally
FEngineList.DAQEngines.UnlockList;
end;
Thanks!
It's upon you which variant you choose. with only tells the compiler where to get members you write in your code. So yes, it is safe, as long as you're accessing members you wanted to. It doesn't affect the runtime.
I would prefer the first way, just without that begin..end block (if I'd be forced to use with), but it's just my personal preference and you are free to write it as you wish:
with FEngineList.DAQEngines.LockList do
try
...
finally
FEngineList.DAQEngines.UnlockList;
end;
Neither variant is to be recommended. If you had to choose between these two, the former is preferable since there is no real need for an extra local variable.
However, with is to be avoided. The problem is that is introduces potential for scope overlap. If the original scope and the object that is the subject of the with have members with the same name, then the with scope hides the outer scope. This catches you out when you add a new member to the subject of the with that happens to have the same name as a member of the outer scope. At best your program won't compile. At worst it compiles and you have a defect. Quite possibly a defect that you don't readily spot.
Code it like this:
uEngines := FEngineList.DAQEngines.LockList;
try
for Idx := 0 to uEngines.Count - 1 do
uEngines.[Idx].Param1 := Cos(2*pi*Idx/uEngines.Count);
....
end;
finally
FEngineList.DAQEngines.UnlockList;
end;
Related: Is Delphi "with" keyword a bad practice?

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;

Resources