How does pass-by-reference work? - pass-by-reference

Pass-by-reference is easy to visualize with languages that use pointers mostly. But in Pascal, I can hardly see how the pointers pass around subroutines as arguments.
For example:
var a: array [0..2] of integer;
i : integer;
procedure swap(var x, y: integer);
var temp: integer;
begin
temp := x;
x := y;
y := temp;
end;
begin
i := 0;
a[i] := 2;
swap(i, a[i]);
end.
Can the swap(i, a[i]); procedural call statement be replaced with this equivalent pseudocode? Is this how interpreters work behind the scenes?
var tmpOldArrayExpression, tmpNewFirst, tmpNewSecond : integer;
tmpOldArrayExpression := i;
(tmpNewFst, tmpNewSnd) := swap(i, a[i]);
i := tmpNewFirst; { 2 }
a[tmpOldArrayEession] := tmpNewSecond; { 0 }

Behind the scenes, the function Swap is implemented as:
function Swap(x, y: ^integer); // or: PInteger
var
temp: integer;
begin
temp := x^;
x^ := y^;
y^ := temp;
end;
And it is in reality (but not syntactically) called like:
i := 0;
a[i] := 2;
swap(#i, #a[i]);
And Pascal is a compiled language. It is (generally) not interpreted.
To read more about this, read my article explaining pointers and references, especially about reference parameters. It is about Delphi, but the same principles apply to most Pascals.

Afaik Pascal is even simpler than C in this regard, because while yes it has separate syntax, but there are no rules about parameters being aliases of each other (which IIRC C does have)

Related

User defined functions with params

In codesys some functions support what in other languages is usually called 'params', i.e. a function that can take a varied amount of similarly typed variables. For example the ADD Operator (function in ladder).
My question is, if there's any way to do the same in user defined functions?
The only idea that I have so far is to take an ARRAY [*] OF SOMETHING and use LOWER_BOUND and UPPER_BOUND to do the computations. This does work, but requires the user to create an additional array variable every time they want to call my function. For example, we have the CONCAT function that concatenates 2 strings. Suppose I want a CONCAT_ALL function that takes n strings and concatenates them all:
STRS: ARRAY [0..9] OF STRING := [STR1, STR2, STR3, STR4, STR5, STR6, STR7, STR8, STR9, STR10];
// This works, but I want to avoid creating an array variable!
CONALL1: STRING := CONCAT_ALL(STRINGS := STRS);
// This doesn't work!
CONALL2: STRING := CONCAT_ALL(STRINGS := [STR1, STR2, STR3, STR4, STR5, STR6, STR7, STR8, STR9, STR10]);
(EDIT: As I was asked, I am using Schneider Electric Machine Expert 1.2, or CODESYS compiler 3.5.12.80)
There is hope in the future!
In Codesys V3.5 SP16 it seems to be finally possible to use FUNCTIONs and METHODs with optional arguments. Of course this will be in non-codesys products, like TwinCAT and Schneider, in later versions.
This means you can finally create a CONCAT with 100 arguments and call it with just for example 3! Awesome.
https://www.codesys.com/fileadmin/data/Images/Download/features-and-improvements-V35SP16-en.pdf
Here is an object oriented example of a string concatenator Function Block:
First we define an Interface with 2 methods:
INTERFACE I_MultipleConcat
METHOD concatString : I_MultipleConcat
VAR_INPUT
sTarget : STRING;
END_VAR
METHOD getResult
VAR_IN_OUT
sRetrieveResult : STRING(1000);
END_VAR
Then the Function Block which implements the Interface:
FUNCTION_BLOCK FB_MultipleConcat IMPLEMENTS I_MultipleConcat
VAR_OUTPUT
uiLen : UINT;
END_VAR
VAR
sResult : STRING(1000);
END_VAR
//-------------------------------------------------------------------
METHOD concatString : I_MultipleConcat
VAR_INPUT
sTarget : STRING;
END_VAR
//make sure that the length of sResult is not exceeded
IF uiLen + INT_TO_UINT(LEN(sTarget)) <= (SIZEOF(sResult)-1)
THEN
//add uiLen as offset to sResult memory access
memcpy(ADR(sResult) + uiLen,ADR(sTarget),LEN(sTarget));
uiLen := uiLen + INT_TO_UINT(LEN(sTarget));
END_IF
//return the instance of this FuncBlock in order to concat new strings
//with concatString() or pass the result to another STRING with getResult()
concatString := THIS^;
//-------------------------------------------------------------------
METHOD getResult
VAR_IN_OUT
sRetrieveResult : STRING(1000);
END_VAR
sRetrieveResult := sResult;
sResult := '';
uiLen := 0;
You can call it like this:
IF NOT bInit
THEN
bInit := TRUE;
//s1 must be a STRING(1000) otherwise compile error
fbMultipleConcat
.concatString('Test1 ')
.concatString('Test2 ')
.concatString('Test3 ')
.getResult(s1);
END_IF
Short answer: There is no way to pass n arguments to a function.
Structured text is a strongly and statically typed language designed for hard real time requirements and it is not a scripting language like Python.
If you have a lot of string manipulations in your code that you don't want to do in python but in your real time loop (and you should assess if it's really necessary depending on your requirements) and still want to make it in a comfortable way, then you have to put some effort in it and build a string manipulation library yourself.
After that you could have a very comfortable function call like this:
sResult := F_Concat6(str1,str2,str3,str4,str5,str6);
I understand that it is tempting to adopt thought and programming patterns learned from other programming languages, but structured text and real time industrial control programming is really another kind of beast compared to common user land programming.
With that I mean, that there are specific reasons why the language is designed as it is and when those principles are correctly understood and applied, rock solid architectures derive from them.
To sum it up, my two cents of advice on this:
Think and write software as expected by your domain and do not port incompatible working methods from other domains.
No you cannot pass n arguments to function.
But you can pass an array, with none fixed number of elements. Syntaxyx for Codesys 2.3.
FUNCTION CONCAT_ALL : STRING(250)
VAR_INPUT
asParts: POINTER TO ARRAY[0..10000] OF STRING(20); (* Array of strings *)
iNum: INT; (* Number of elements *)
END_VAR
VAR
iCount: INT; (* For cycle *)
END_VAR
FOR iCount := 0 TO 10000 DO
IF iCount > iNum THEN
EXIT;
END_IF;
CONCAT_ALL := CONCAT(CONCAT_ALL, asParts^[iCount]);
END_FOR;
END_FUNCTION
PROGRAM PLC_PRG
VAR
(* Array 1 to test *)
asTest1: ARRAY[1..2] OF STRING(20) := 'String 1', 'String 2';
(* Array 2 to test *)
asTest2: ARRAY[1..3] OF STRING(20) := 'String 1', 'String 2', 'String 3';
s1: STRING(250);
s2: STRING(250);
END_VAR
s1 := CONCAT_ALL(ADR(asTest1), 2);
s1 := CONCAT_ALL(ADR(asTest2), 3);
END_PROGRAM

Why is implicit conversion between anonymous access objects disallowed in Ada?

I am working my way through Barnes' book 'Programming in Ada 2012'. This is a code sample implementing a stack from section 12.5.
src/stacks.adb: (the main relevant file)
package body Stacks is
procedure Push(S: in out Stack; X: in Integer) is
begin
S := new Cell'(S,X);
end Push;
procedure Pop(S: in out Stack; X: in out Integer) is
begin
X := S.Value;
S := Stack(S.Next);
end Pop;
function "="(S, T: Stack) return Boolean is
SS: access Cell := S;
TT: access Cell := T;
begin
while SS /= null and TT /= null loop
if SS.Value /= TT.Value then
return false;
end if;
SS := SS.Next;
TT := TT.Next;
end loop;
return SS = TT; -- error: implicit conversion of stand-alone anonymous access object not allowed
end "=";
end Stacks;
I have added a comment containing the error that gnat gives me. Why am I not allowed to convert from one anonymous access Cell to another?
I can solve the problem by inverting the condition:
return not (SS /= TT);
It mystifies me as John Barnes states earlier that if you define a "=" operator returning a boolean, then the inverse "/=" is generated automatically for you, meaning the opposite.
Similarly, the loop condition can be inverted, in which case it fails to compile with the same message.
Finally, a side-note: the expected behaviour of the program, which it gives after changing to return not (SS /= TT) is to recurse infinitely and raise a storage_error due to stack overflow. The reason for that is better seen in this other SO question, and is not the subject of this question.
Why is the conversion disallowed by the compiler when I write "="?
Why is it different when I write "/=", which I thought would always be the inverse?
The other files needed in order to compile the example for yourself:
src/stacks.ads:
package Stacks is
type Stack is limited private;
procedure Push(S: in out Stack; X: in Integer);
procedure Pop(S: in out Stack; X: in out Integer);
function "="(S, T: Stack) return Boolean;
private
type Cell is
record
Next: access Cell;
Value: Integer;
end record;
type Stack is access all Cell;
end;
src/main.adb:
with Ada.Text_IO; use Ada.Text_IO;
with Stacks; use Stacks;
procedure Main is
A : Stack;
B : Stack;
begin
Push(A, 1);
Push(B, 1);
Push(A, 2);
Push(B, 2);
Push(A, 1);
Push(B, 1);
Push(A, 8);
Push(B, 8);
declare
Same : Boolean := A = B;
Text : String := (if Same then "They are the same" else "They are not the same");
begin
Put_Line(Text);
end;
end Main;
stacks.gpr:
project stacks is
for Source_Dirs use ("src");
for Object_Dir use "obj";
for Main use ("main.adb");
end stacks;
Makefile:
all:
gprbuild -d -p -g
clean:
rm -rf obj *.o *.ali
Or compile with gcc:
gcc -c src/*.adb
gnatbind main
gnatlink main
It gives the same results.

Initialize Array of Custom Types in Structured Text Syntax

In my project I have a type like:
TYPE myDataStruct :
STRUCT
A : UINT;
B : WORD;
C : REAL;
D : Custom_Obj;
END_STRUCT
END_TYPE
And I need to keep an array of this type for persistent memory. I can't just use VAR RETAIN because this particular piece of memory needs to persist through a download. The controller I am using has a way to do this but in order for it to work I need to be able to set the array equal to an initial value. So if I have declared
myarray := ARRAY[0..20] OF myDataStruct;
How do I then initialize this array to a blank array? What is the equivalent of new in other languages?
I have guessed
myarray := [21(A := 0,
B := '',
C := 0.0,
D := ??? )];
But that doesn't appear to be right. It could be simplified if there were only one level deep of custom structs and for this application I could do that. However, I still don't think I have the syntax right.
What is the equivalent of new in other languages?
The analog of this is
VAR
EmptyArray : ARRAY[0..20] OF myDataStruct;
END_VAR
If you want to pre-populate it with default values
VAR
EmptyArray : ARRAY[0..20] OF myDataStruct := [
(A := 100, B := 200, С := 0.0, D := ???),
(A := 34, B := 45, С := 0.1, D := ???),
..... etc
];
END_VAR
For CoDeSys 2.3 delete [ and ].
What you have to understand that EmptyArray is not a prototype of data you need but already initialized variable.
There is no way to initialize it in "x = new struct()" way. You also can't assign the whole array in the code with something like myarray = [1, 2, 3] etc, as far as I know.
If you just want to set it empty with values like 0, '', etc, then there are two ways that I would use:
1. Use MEMSET function to set all bytes to 0
Link to the online help
//Something like
MemSet(
pbyBuffer := ADR(myarray), //Address of the variable
byValue := 0, //Byte that address is filled with
dwSize := SIZEOF(myarray) //How many bytes? (variable size)
)
2. Create a dummy variable and assign it to the myarray
The variable is always initialized to zeros, so EmptyArray values are all 0/empty etc.
VAR
EmptyArray : ARRAY[0..20] OF myDataStruct;
END_VAR
//In the code
myarray := EmptyArray;
I hope I understood your question correctly.

Split IEC 61131-3 DINT into two INT variables (PLC structured text)

I want to publish a DINT variable (dintTest) over MODBUS on a PLC to read it with Matlab Instrument Control Toolbox. Turns out, Matlab can read Modbus variables but only INT16. So i want to split the DINT variable into two INT variables in IEC. I found this solution, but this only allows values from +- 0 ... 32767^2:
dintTest := -2;
b := dintTest MOD 32767;
a := dintTest / 32767;
result := 32767 * a + b;
c := DINT_TO_INT(b); // publish over modbus
d := DINT_TO_INT(a); // publish over modbus
What would be the solution for the whole range of DINT?
Thanks!
edit:
I read with a matlab function block in simulink (requires Instrument Control Toolbox):
function Check = MBWriteHoldingRegs(Values,RegAddr)
coder.extrinsic('modbus');
m = modbus('tcpip', '192.169.237.17');
coder.extrinsic('write');
write(m,'holdingregs',RegAddr,double(Values),'int16');
Check = Values;
I would better split DINT to 2 WORD
VAR
diInt: DINT := -2;
dwTemp: DWORD;
w1: WORD;
w2: WORD;
END_VAR
dwTemp := DINT_TO_DWORD(diInt);
w1 := DWORD_TO_WORD(dwTemp);
w2 := DWORD_TO_WORD(SHR(dwTemp, 16));
And then I could build it back in matlab.
The point here is not using mathematic but bit masks.

Digital Metaphors Report Builder 11.05: why my DELPHI code crashes without any error?

Now I build a template for our invoice printer.
But I do really not know, why it crashes without any error.
My goal is to separate the String ItemName at the ';' and print each part into a new line to a Memo1.
procedure DetailBeforeGenerate;
var
s1: String;
s2: String;
wordcount: Integer;
notelength: Integer;
begin
s1 := plPrintInvLine['ItemName'];
notelength := Length(s1);
while notelength > 0 do
begin
notelength := Length(s1);
wordcount := Pos(';' , s1);
s2 := Copy(s1, 0, wordcount-1);
Memo1.Lines.Add(s2);
Delete(s1, 0, wordcount);
end;
end;
See comments below regarding accessing index[0] in a string and thanks to David Heffernen and Ken White. BUT:
Looks like you've got an infinite loop in your code:
notelength := Length(s1);
while notelength > 0 do
begin
notelength := Length(s1);
wordcount := Pos(';' , s1);
s2 := Copy(s1, 0, wordcount-1);
Memo1.Lines.Add(s2);
Delete(s1, 0, wordcount);
end;
Delete(s1, 0, wordcount); Has no effect! Try it in Delphi debugger. Result? notelength is never decremented so you'll loop forever. ' Delete(s1, 0, wordcount);' does not blow up but neither does it delete. Use Delete(s1,1, wordcount) instead.
Index[0] in Delphi strings does not contain your character data - it's 'not accessible' according to the compiler, if you try compiling myString[0];
Also: the way your code is written, you MUST terminate with ';' or a string such as this:
s1 := 'mikey;was;here;a'; will loop infinitely on the last string after ';' ('a')
I also use ReportBuilder templates, etc: In Delphi itself you will not be able to compile MyString[0], but the copy and delete methods are protected from this error, (as David explained) however it appears from what I saw in the debugger that 'Delete(s1, 0, wordcount)' will not throw an exception but fails to delete. So I would not expect RBuilder to be any better, and perhaps worse - copy() may also be failing on string[0] in RAP.
RAP is NOT Delphi - it is a Runtime scripting environment that runs in your template, based on Object Pascal, but it does not support everything, and you cannot always expect it to behave exactly like Delphi.
BTW - ReportBuilder is now up to version 14.0X - if possible you should upgrade - there have been a lot of improvements in the RAP environment. In a later version your code might work OK or you'll get back an error message from RAP.
Also: If you want to debug in RAP it's not so easy. But to give you a clue as to where the error might be occurring, put a text label on your report and after each line of your code add
mylabel.caption:='statementxxx ran';
or
mylabel.caption:= myVariable.value;
Etc. That will give you a little ad hoc tracer - maybe show you where/why you failed, etc.
For all searching people: I found the solution with the excellent help of this community!
The working code looks like this:
procedure DetailBeforeGenerate;
var
S1: String;
S2: String;
wordcount: Integer;
notelength: Integer;
begin
S1 := plPrintInvLine['ArtName'];
notelength := Length(S1);
while (notelength > 0) do
begin
wordcount := Pos(';',S1);
S2 := Copy(S1, 1, wordcount-1);
if ( Pos(' ',S2) = 1 ) then Delete(S2, 1, 1);
Memo1.Lines.Add(S2);
Delete(S1, 1, wordcount);
notelength := notelength - wordcount;
end;
end;