Sample Applications of gmSL
This section contains samples of
gmSL applications.
Migration of VB6 RDO code to ADO.NET
The original request from the user was "Our VB6 code is in the following pretty standard pattern:"
Code Block |
---|
language | vb |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Dim SQL As String
Dim SP As rdoQuery
Dim Results As rdoResultset
SQL = "select * from departments where category_id = ?"
Set SP = MainForm.EnablerRDOCN.CreateQuery("QueryDept", SQL)
SP.rdoParameters(0).value = CategoryID;
Set Results = SP.OpenResultset(rdOpenForwardOnly)
While Not Results.EOF
DeptID = Results.rdoColumns("department_id").value
Results.MoveNext
Wend
SP.Close
|
"I would like it to produce code like this, which is roughly standard ADO.NET connected db access:"
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
string SQL = "select * from departments order by department_id";
DbCommand SP = DatabaseConnection().CreateCommand();
SP.CommandText = SQL;
SP.Parameters[0].Value = CategoryID;
using (DbDataReader Results = SP.ExecuteReader())
{
while(Results.Read())
{
DeptID = Convert.ToInt32(Results["department_id"]);
}
}
|
"You can see that is a pretty drastic translation. What sort of effort are we talking to implement that? And what sort of technology area within gmStudio would you use?"
An example code was created
An example VB6 code was created that uses the external library "MSRDO20.DLL#Microsoft Remote Data Object 2.0". Some time was spent to make it a real working example using the .NET class "System.Data.SqlClient", that could passed on to show how
gmBasic is able to perform these types of code transformations. The two specific VB6 methods to be transformed are as follows.
Code Block |
---|
language | vb |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Public Sub connectDB()
Set conn = New rdoConnection
Dim dbs As String
_ dbs = _
"UID=stocks_login;PWD=password;Database=stocks;" _
& "Server=GMI-CS-01.gmi.local;Driver={SQL Server};" _
& "DSN='';"
conn.Connect = dbs
conn.EstablishConnection
End Sub
Public Sub execQuery()
Dim SQL As String
Dim SP As rdoQuery
Dim Results As rdoResultset
Dim f As String
SQL = "select * from accounts where accountID < ? and FirstName like ?"
Set SP = conn.CreateQuery("QueryAcct", SQL)
SP.rdoParameters(0).Value = "5005"
SP.rdoParameters(1).Value = "Test%"
Set Results = SP.OpenResultset(rdOpenForwardOnly)
While Not Results.EOF
f = Results.rdoColumns("FirstName").Value
writeLog f
f = Results.rdoColumns("eMail").Value
writeLog f
Results.MoveNext
Wend
SP.Close
End Sub
|
Following the request supplied by the user as closely as possible, the target translations for these two would be
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
public static void connectDB()
{
conn = new System.Data.SqlClient.SqlConnection();
string dbs = "";
dbs = "UID=stocks_login;PWD=password;Database=stocks;Server=GMI-CS-01.gmi.local;";
conn.ConnectionString = dbs;
conn.Open();
}
public static void execQuery()
{
string SQL = "";
System.Data.SqlClient.SqlCommand SP = null;
string f = "";
SQL = "select * from accounts where accountID < @0 and FirstName like @1";
(SP = conn.CreateCommand()).CommandText = SQL;
SP.Parameters.Add(new System.Data.SqlClient.SqlParameter("@0", null)).Value = "5005";
SP.Parameters.Add(new System.Data.SqlClient.SqlParameter("@1", null)).Value = "Test%";
using (System.Data.SqlClient.SqlDataReader Results = SP.ExecuteReader())
{
while (Results.Read())
{
f = Results["FirstName"].ToStr();
writeLog(f);
f = Results["eMail"].ToStr();
writeLog(f);
}
}
}
|
The unmigrated translation
Using the reference script for
MSRDO20.Dll exactly as produced by the
gmBasic IDL translator and the following very simple
gmSL based translation script
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
<gmBasic>
<gmSL>
Execute.Storage Action:="Create", Identifier:="\gmManual\Samples\RDOtoNet\csh\RDOToNet_std"
Select.DevEnv = "VS2013"
Select.Dialect = Dialects.csh
Select.BuildFile = BuildFileStatus.On
Select.System = "\gmManual\Samples\FromIdl"
Select.Target = "\gmManual\Samples\FromIdl"
Select.DeployLocation = "\Temp"
Execute.Compile Project:="\gmManual\Samples\RDOtoNet\src\RDOToNET.vbp"
Execute.Analyse
Execute.Output Status:="New", Filename:="\gmManual\Samples\RDOtoNet\csh\RDOToNet_std.bnd"
Execute.Author
Execute.Storage Action:="Close"
</gmSL>
</gmBasic>
|
the following unmigrated translation is produced.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
public static void connectDB()
{
conn = new RDO.rdoConnection();
string dbs = "";
dbs = "UID=stocks_login;PWD=password;Database=stocks;" + "Server=GMI-CS-01.gmi.local;Driver={SQL Server};" + "DSN='';";
conn.Connect = dbs;
conn.EstablishConnection(null,null,null);
}
public static void execQuery()
{
string SQL = "";
RDO.rdoQuery SP = null;
RDO.rdoResultset Results = null;
string f = "";
SQL = "select * from accounts where accountID < ? and FirstName like ?";
SP = conn.CreateQuery("QueryAcct",SQL);
SP.rdoParameters[0].Value = "5005";
SP.rdoParameters[1].Value = "Test%";
Results = SP.OpenResultset(RDO.ResultsetTypeConstants.rdOpenForwardOnly,null,null);
while (!Results.EOF)
{
f = Results.rdoColumns["FirstName"].Value.ToStr();
writeLog(f);
f = Results.rdoColumns["eMail"].Value.ToStr();
writeLog(f);
Results.MoveNext();
}
SP.Close();
}
|
Adding a few migration names
The first step in any migration is to add migration names, using
gmPL, to reflect any simple name changes that need to be done. The reader is assumed to be familiar with the
gmPL component refactoring attributes -- in this case
migName and
migPattern. Doing this takes a lot of noise out of the differences between the migrated and unmigrated translations. In this case, they are as summarized here.
Code Block |
---|
language | xml |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
<library id="MSRDO20.DLL" name="RDO" migName="System.Data.SqlClient" .. >
<class id="_rdoConnection" ..>
<property id="Connect" type="String" status="InOut" migName="ConnectionString" />
<method id="EstablishConnection" type="Void" migPattern="%1d.Open()\c">
<method id="CreateQuery" type="rdoQuery" migName="CreateCommand">
...
<class id="rdoPreparedStatement"
<property id="rdoParameters" type="rdoParameters" status="Out" migName="Parameters" />
<method id="OpenResultset" type="rdoResultset" migPattern="%1d.ExecuteReader()" >
...
<coclass id="rdoConnection" migName="SqlConnection">
<coclass id="rdoResultset" creatable="off" migName="SqlDataReader">
<coclass id="rdoQuery" migName="SqlCommand">
|
With these changes in place, the unmigrated translations are now as follows.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
public static void connectDB()
{
conn = new System.Data.SqlClient.SqlConnection();
string dbs = "";
dbs = "UID=stocks_login;PWD=password;Database=stocks;" + "Server=GMI-CS-01.gmi.local;Driver={SQL Server};" + "DSN='';";
conn.ConnectionString = dbs;
conn.Open();
}
public static void execQuery()
{
string SQL = "";
System.Data.SqlClient.SqlCommand SP = null;
System.Data.SqlClient.SqlDataReader Results = null;
string f = "";
SQL = "select * from accounts where accountID < ? and FirstName like ?";
SP = conn.CreateCommand("QueryAcct",SQL);
SP.Parameters[0].Value = "5005";
SP.Parameters[1].Value = "Test%";
Results = SP.ExecuteReader();
while (!Results.EOF)
{
f = Results.rdoColumns["FirstName"].Value.ToStr();
writeLog(f);
f = Results.rdoColumns["eMail"].Value.ToStr();
writeLog(f);
Results.MoveNext();
}
SP.Close();
}
|
The remaining work involves adding a refactoring specification and finally
gmSL code.
The first difference between the current translation and the target translation is in the method
connectDB.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Current: dbs = "UID=stocks_login;PWD=password;Database=stocks;" + "Server=GMI-CS-01.gmi.local;Driver={SQL Server};" + "DSN='';";
Target : dbs = "UID=stocks_login;PWD=password;Database=stocks;Server=GMI-CS-01.gmi.local;";
|
The character string, which consists of a series of "name=value;" pairs has been changed to remove the
Driver pair and the
DSN pair.
The first question that has to be asked is "How do we determine which character strings need to be changed?". In an editing context, perhaps all strings could be searched for these pairs and they could be universally removed. This would almost certainly work in this case, but in general changes need to be contingent on their use. The string above needs to be changed because in this later statement
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
conn.ConnectionString = dbs;
|
it is being assigned to a connection string. Connection strings in the target environment do not have these two attributes. A search needs to be made of assignments to the
ConnectionString property, and the strings used in these assignments need to be edited, if possible and necessary.
Transformation is not a process that works with source code or target code. It works with the intermediate code produced by the compiler. After the compiler has completed and after the analyser has finished the standard migrations built into
gmBasic, references in the intermediate code to components in external libraries are scanned looking for those that have user supplied code registered for them. The purpose of that user code is to change that intermediate code so that it performs the target operation as opposed to its original source operation.
Once a statement that requires transformation has been identified, the first step is to examine the intermediate code currently produced for it. This can be seen in an audit report of the
vbi file produced by the translation script or can be produced on-the-fly using the
Opcode.DumpCode method. The intermediate code for the conectionString assignment statement is as follows.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
72 | | | | NEW | 43 conn.Connect = dbs
75 | | | | LEV | Nest0
77 | 1.77 | 1.77 | String | LDA | Variable:dbs:88066
82 | 1.77 | | | ARG | String
84 | 2.84 | 1.84 | RDO.rdoConnection | LDA | Variable:conn:87690
89 | 3.89 | 1.84 | String | LLP | Component:Connect:50673
94 | 2.84 | 1.84 | String | MEM | Child
96 | | | | STR | AssignValue
|
It can be seen that the external library component receiving the content of the variable dbs is
Component:Connect:50673. Looking this component up in the symbol audit shows that it is
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
| 50673 | 39973 | Lib_Property | RDO._rdoConnection.Connect
|
Starting the actual transformation process, then, will involve writing a
gmSL method that will be notified of all code references to
Lib_Property RDO._rdoConnection.Connect. The purpose of that method will be to locate any strings being assigned to that property and to edit them to remove any unwanted attributes.
The
gmSL transform methods are introduced within library description file
Refactor statement sections using the
gmSL statement. In this case this is as follows.
Code Block |
---|
language | xml |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
<Refactor id="RDO" event="rdoHandlers" >
<gmSL NameSpace="rdoHandlers" Class="Transform" Source="msrdo20Transform.gmsl" />
</Refactor>
|
The namespace for these methods is the
event name used in the
refactor statement and the class for the methods is
Transform. Remember that in real migration projects many different libraries and codes are being migrated; therefore, careful naming conventions are necessary. The actual
gmSL code could be embedded within the
gmSl statement; however, there are "intellisense" editors available for files that have the
gmsl extension, so keeping this code separate makes it easier to author and maintain it.
For now the file
msrdo20Transform.gmsl is simply as follows.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
int __rdoConnection_Connect(int subRoot,int iStart,int iRefer)
{
System.LogMessage("RDO#01: sub<" + Symbol.FullName(subRoot,-1) + "> iStart=" + iStart + " iRefer=" + iRefer);
return 0;
}
|
It simply logs a message to the translation log file and
returns a zero indicating that no change has been made by the method.
The names of transpose methods are an "underline converted" form of the host relative identifier of the component whose reference code is to be transformed. Underline conversion changes all periods in the identifier to an underscore and changes all underscores in the identifier to double underscores. In this case the component identifier is
RDO._rdoConnection.Connect. Making it host relative removes the leading
RDO. and doing the underline conversion makes it
__rdoConnection_Connect. This conversion is necessary to create unique but well-formed method identifiers. After the
gmSL file is compiled,
gmBasic scans the refactor host for components that match the underscore converted methods, sets their
hasCodeHandler property
True, and sets their
migTransform member equal to the root of the transform method.
All transform methods have three parameters. The parameter
subRoot is the root of the source code component that references the component being transformed. The parameter
iStart is a starting code location that marks where the referencing code began. Its exact value will vary by the type of reference. The parameter
iRefer is the code location of the actual reference to the transform component. To bring this into focus, the initial version of the transform method merely logs the content of these parameters. Running the translation script with
Progress="1" produces the following log.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Loading reference:[Language.std] \gmIDF\Language.std.xml
Reprocessing file: Transform.cls
Reprocessing file: Transform.cls
Processing file: \gmManual\Samples\RDOtoNet\src\RDOToNET.vbp
Loading reference:[stdole2.tlb] \gmIDF\stdole2.tlb.xml
Loading reference:[MSRDO20.DLL] \gmManual\Samples\FromIdl\MSRDO20.DLL.xml
Reprocessing file: \gmManual\Samples\RDOtoNet\src\modRDOToNET.bas
Reprocessing file: \gmManual\Samples\RDOtoNet\src\modRDOToNET.bas
RDO#01: sub<RDOToNET.connectDB> iStart=77 iRefer=89
|
Focusing first on the actual message produced by the transform method, it confirms what was seen in the code dump earlier. The method
connectDB contains a reference to
RDO._rdoConnection.Connect at code offset
89 and that a good starting point is at offset
77 which in this case is the offset of the reference to the variable
dbs whose content will eventually be evaluated and modified.
The log more importantly brings out the integration between the
gmSL transpose capability and the overall processing of the translation script. Translation, when it becomes migration, is a very complex, iterative process. Things go wrong. Things do not work as expected. The translation produces a log file that describes what happens, and it also produces a
vbi file that contains all of the detailed information about how the source code was migrated and what it was migrated into. When things go wrong it is important to have available an exact representation of the logic used to do the migration in the
vbi file that actually produced it. Note in lines 05 and 06 above that the transpose class is being compiled in the same manner as the source code. All code associated with it is in the
vbi file where it can be audited and examined in precisely the same manner as any other code. The translation produced is identical to the one before the refactor section was added, but the
vbi is different. First the transform method itself has been added into the symbol table.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
2 | 85026 | 81920 | ClassFile | Transform.cls
3 | 85249 | 85026 | Subprogram | rdoHandlers.Transform.__rdoConnection_Connect
4 | 85337 | 85249 | Variable | rdoHandlers.Transform.__rdoConnection_Connect.subRoot
4 | 85418 | 85249 | Variable | rdoHandlers.Transform.__rdoConnection_Connect.iStart
4 | 85463 | 85249 | Variable | rdoHandlers.Transform.__rdoConnection_Connect.iRefer
2 | 85128 | 81920 | Vb_Name.85026 | rdoHandlers.Transform
|
The source code for the method can be viewed if the
EchoInput Select attribute is turned on.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Text Associated with Scanned Code:
RecNo | Rai | nRec | Content
----- | --- | ---- | -------
1 | 0 | 65 | int __rdoConnection_Connect(int subRoot,int iStart,int iRefer){
2 | 0 | 110 | System.LogMessage("RDO#01: sub<" + Symbol.FullName(subRoot,-1) + "> iStart=" + iStart + " iRefer=" + iRefer)
3 | 0 | 10 | return 0
4 | 0 | 3 | }
|
An actual code dump of the transpose method can be examined.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Actual C# Codeblock Associated with __rdoConnection_Connect:
Offset | Sl.Start | Ql.Start | Quantity type | Opcode | Operation support information
------ | -------- | -------- | ------------- | ------ | -----------------------------
0 | | | | LEV | Nest0
2 | 1.2 | 1.2 | String | LSC | 12:RDO#01: sub<
7 | 1.2 | 1.2 | String | LEV | Nest1
9 | 2.9 | 2.9 | Integer | LDA | Variable:subRoot:85337
...
59 | 2.9 | | | SCM | System_LogMessage
61 | 2.9 | | | LEV | Nest0
63 | 3.63 | 1.63 | Integer | LIC | 0
66 | 3.63 | | | ARG | Integer
68 | | | | EXI | Function
|
Note that the
gmSL, though it has a very different syntax than
VB6, uses the identical
gmIL operations. Finally, the actual entries for the migrated component have been updated to mark it has having a code handler whose offset is
85249 which is this method.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Detailed Description of Lib_Property RDO._rdoConnection.ConnectionString with root address 50673:
Property | Content
-------- | -------
Migrate Status | Referenced
Migrate Flags | HasCodeHandler
Transformation | 85249
BinaryType | String:8
|
The actual code used to ultimately change the content of the
dbs connection string works through references to the
RDO._rdoConnection.Connect component. It is as follows. It begins with the same underlined converted method identifier, with the same standard parameters, and then the declarations of the local variables. Reading about, writing about, and understanding code whose purpose is to manage other code can get confusing quickly, so keep in mind that there are two coding levels.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
int __rdoConnection_Connect(int subRoot,int iStart,int iRefer)
{
tCodeBlock codptr;
int nCode;
int opcd;
int subcd;
int icode;
int iEnd;
int localVar;
int iAssign;
string connect;
int iPos;
int semi;
int nDelete;
int addr;
|
Though not in this sample, there could certainly be other references to the
Connect property that are not assignments from local variables. So the first step is to verify that this is a local variable assignment to the property.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
codptr = Opcode.GetCode();
nCode = Opcode.GetLength();
opcd = Opcode.GetOperation(codptr,iStart,localVar);
if(opcd != OPC.LDA) return 0;
icode = iRefer + sizeof(OPC.LLP);
opcd = Opcode.GetOperation(codptr,icode,subcd);
if(opcd != OPC.MEM) return 0;
icode = icode + sizeof(OPC.MEM);
opcd = Opcode.GetOperation(codptr,icode,subcd);
if(opcd != OPC.STR) return 0;
|
Remember that the
gmSL itself is stored in the same overall structure as the user code; therefore, the first two calls in almost every transpose method use the
Opcode methods
GetCode and
GetLength that reference the user code and not the running code. Next the method checks that the
iStart parameter is a reference to a local variable and that the property reference is an assignment. From the dump the expected code sequence is.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
89 | 3.89 | 1.84 | String | LLP | Component:Connect:50673
94 | 2.84 | 1.84 | String | MEM | Child
96 | | | | STR | AssignValue
|
Note that the call that checked for the local variable assignment also trapped its root in the
localVar. The next step is to look for a preceding assignment to this local variable.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
iAssign = RefactorCode_FindAssign(localVar,iStart);
if(iAssign == 0) return 0;
|
The code for the
RefactorCode_FindAssign method is shown below. If it returns a nonzero value, then that value is the code offset of the value being assigned to the variable. The next step is to obtain the actual string value being assigned to the local variable.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
iEnd = Opcode.FindArgumentEnd(codptr,iAssign,nCode);
connect = Opcode.GetString(iAssign,iEnd);
if(!connect) return 0;
|
The method
Opcode.GetString is passed the starting and ending offset of code that may produce a string constant when it is executed. The actual code being passed to
GetString is
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
37 | | | | LEV | Nest0
39 | 1.39 | 1.39 | String | LSC | 46:UID=stocks_login;PWD=password;Database=stocks;
44 | 2.44 | 2.44 | String | LSC | 47:Server=GMI-CS-01.gmi.local;Driver={SQL Server};
49 | 1.39 | 1.39 | String | CAT | String
51 | 2.51 | 2.51 | String | LSC | 7:DSN='';
56 | 1.39 | 1.39 | String | CAT | String
58 | 1.39 | | | ARG | String
|
The method
Opcode.GetString literally executes this code, even though it is user code, using the same engine as is used to execute the
gmSL code. Since this code is being executed at compile time, it may not be possible to resolve it into a string -- if it contains variable references. If it can resolve it, it returns the string; if not, it returns a null-string. In this case the variable
connect contains a resolved connection string which it can edit by removing the attribute-values pairs that are not to be used.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
iPos = Character.FindFirst(connect,0,"Driver=");
if(iPos)
{
iPos = iPos - 1;
semi = Character.FindFirst(connect,iPos,";");
if(semi)
{
connect = Character.Remove(connect,iPos,semi);
}
}
iPos = Character.FindFirst(connect,0,"DSN=");
if(iPos)
{
iPos = iPos - 1;
semi = Character.FindFirst(connect,iPos,";");
if(semi)
{
connect = Character.Remove(connect,iPos,semi);
}
}
|
The final step is to replace the old string expression with the revised string. Note that this replacement may well shift code that precedes the referencing code. The calling method that is scanning for transform references needs to be told that this has happened.
A non-zero return value tells the scanner that the method has made a change in the code and that scanning should resume at the indicated code location.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
nDelete = RefactorCode_ReplaceAssign(iAssign,connect);
iRefer = iRefer - nDelete;
return iRefer;
}
|
Moving now to the method
RefactorCode_FindAssign, note that it does not contain an underline converted identifier so it is simply private to this class. Its parameters are the root offset of the variable for which an assignment is sought and the code location that the assignment must precede.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
int RefactorCode_FindAssign(int varRoot,int iEnd)
{
tCodeBlock codptr;
int lastLev0;
int icode;
int addr;
int opcd;
int subcd;
codptr = Opcode.GetCode();
lastLev0 = 0;
for(icode = 0; icode >= 0; icode = Opcode.GetNext(codptr,icode,iEnd))
{
opcd = Opcode.GetOperation(codptr,icode,subcd);
if(opcd == OPC.LEV && subcd == 0) lastLev0 = icode;
else if(opcd == OPC.LDA && subcd == varRoot)
{
opcd = Opcode.GetOperation(codptr,icode+sizeof(OPC.LDA),subcd);
if(opcd == OPC.STR) return lastLev0;
}
}
return 0;
}
|
It consists of a simple scan from the front of the user method code for an assignment to the indicated variable. If found it returns the starting offset of the expression that defines the value being assigned.
The method
RefactorCode_ReplaceAssign deletes the original code in the expression and then inserts a reference to the replacement string.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
int RefactorCode_ReplaceAssign(int iAssign, string replacement)
{
tCodeBlock codptr;
int nCode;
int iEnd;
int nDelete;
int addr;
codptr = Opcode.GetCode();
nCode = Opcode.GetLength();
iEnd = Opcode.FindArgumentEnd(codptr,iAssign,nCode);
iAssign = iAssign + sizeof(OPC.LEV);
nDelete = iEnd - iAssign - sizeof(OPC.LSC) - sizeof(OPC.ARG);
if(nDelete > 0)
{
nCode = Opcode.DeleteCode(iAssign,nCode,nDelete);
Opcode.SetLength(nCode);
}
addr = Store.String(replacement);
Opcode.SetOperation(codptr,iAssign,OPC.LSC,addr);
return nDelete;
}
|
When doing code substitution, the most difficult step is deciding how much code should be deleted or inserted. In this case, when
nDelete is computed,
iEnd contains the location immediately after the
ARG operation that closes the expression code and
iAssign contains the location immediately after the
LEV operation that opens the expression code. Thus,
iEnd - iAssign needs to be offset by the size of the
ARG operation which is needed in the new expression and by the size of the
LSC operation which will load the new string.
Running this new code does now produce the desired change as the following file comparison shows.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Comparing files rdotran.bnd and RDOTRAN.SAV
***** rdotran.bnd
dbs = "UID=stocks_login;PWD=password;Database=stocks;Server=GMI-CS-01.gmi.local;";
***** RDOTRAN.SAV
dbs = "UID=stocks_login;PWD=password;Database=stocks;" + "Server=GMI-CS-01.gmi.local;Driver={SQL Server};" + "DSN='';";
*****
|
The next difference between the unmigrated and migrated code is in
execQuery in the string assigned to the variable
SQL.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Current: SQL = "select * from accounts where accountID < ? and FirstName like ?";
Target : SQL = "select * from accounts where accountID < @0 and FirstName like @1";
|
As with the previous string change requirement the fact that this string should change is determined from the fact that the variable
SQL is used as the second argument to the method
RDO._rdoConnection.CreateQuery.
Code Block |
---|
language | vb |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Set SP = conn.CreateQuery("QueryAcct", SQL)
|
Though the reference pattern is different and the actual editing is different, the logic of the transform method is about the same as the logic of the
RDO._rdoConnection.Connect method. The name of the method is now constructed to refer to
CreateQuery, the parameters are the same three. The declaration of the method is followed by the declarations of the local variables.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
int __rdoConnection_CreateQuery(int subRoot,int iStart,int iRefer)
{
tCodeBlock codptr;
int nCode;
int opcd;
int subcd;
int icode;
int sqlString;
int sqlVar;
string query;
int iAssign;
int index;
int iPos;
int lPos;
int nDelete;
int createCommand;
int iEnd;
|
The first step is to make certain that this reference is a valid call to the method whose second argument is a variable reference. The code is a bit long but straightforward.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
codptr = Opcode.GetCode();
nCode = Opcode.GetLength();
icode = iRefer;
opcd = Opcode.GetOperation(codptr,icode,subcd);
if(opcd != OPC.LLP) return 0;
icode = icode + sizeof(OPC.LLP);
opcd = Opcode.GetOperation(codptr,icode,subcd);
if(opcd != OPC.MEM) return 0;
icode = icode + sizeof(OPC.MEM);
opcd = Opcode.GetOperation(codptr,icode,subcd);
if(opcd != OPC.LEV) return 0;
sqlString = Opcode.FindArgumentEnd(codptr,icode,nCode);
opcd = Opcode.GetOperation(codptr,sqlString,subcd);
if(opcd != OPC.LEV) return 0;
sqlString = sqlString + sizeof(OPC.LEV);
opcd = Opcode.GetOperation(codptr,sqlString,sqlVar);
if(opcd != OPC.LDA) return 0;
sqlString = sqlString+sizeof(OPC.LDA);
opcd = Opcode.GetOperation(codptr,sqlString,subcd);
if(opcd != OPC.ARG) return 0;
sqlString = sqlString+sizeof(OPC.ARG);
|
The highlighted code shows where the root of the
SQL variable in
sqlVar is determined. The second step is to find the preceding assignment to this variable.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
iAssign = RefactorCode_FindAssign(sqlVar,iRefer);
if(iAssign == 0) return 0;
|
The third step is to obtain the actual string value being assigned to the local variable.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
iEnd = Opcode.FindArgumentEnd(codptr,iAssign,nCode);
query = Opcode.GetString(iAssign,iEnd);
if(!query) return 0;
|
If there is a constant query string, then the fourth step is to relace the "?"s with @index and if necessary replace it in the code.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
if(iAssign)
{
iPos = 0;
for(index = 0; index < 10; index = index + 1)
{
lPos = Character.FindFirst(query,iPos,"?");
if(!lPos) break;
iPos = iPos + lPos - 1;
query = Character.Remove(query,iPos,1);
query = Character.Insert(query,iPos,"@" + index);
}
if(iPos != 0)
{
nDelete = RefactorCode_ReplaceAssign(iAssign,query);
iRefer = iRefer - nDelete;
sqlString = sqlString - nDelete;
}
}
return iRefer;
}
|
Running this code causes the desired change in the translation as the following file comparison shows.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Comparing files rdotran.bnd and RDOTRAN.SAV
***** rdotran.bnd
SQL = "select * from accounts where accountID < @0 and FirstName like @1";
***** RDOTRAN.SAV
SQL = "select * from accounts where accountID < ? and FirstName like ?";
*****
|
In addition to having to change the content of the query string, the actual call to the method needs to be changed into a combined method call followed by a propery assignment. The actual difference is
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Current: SP = conn.CreateCommand("QueryAcct",SQL);
Target : (SP = conn.CreateCommand()).CommandText = SQL;
|
The easiest way to acheive these types of changes is to invent a new method that reflects the revised method and then to associate with that method the final
migPattern. These new methods are placed in a
migClass defined within the refactor section. The added declaration is
Code Block |
---|
language | xml |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
<migclass id="DotNet">
<method id="CreateCommand" type="void" migPattern="(%1d = %2d()).CommandText = %4d\c">
<argument id="conn" type="Object" status="ByVal" />
<argument id="Name" type="String" status="ByVal"/>
<argument id="SqlString" type="Variant" status="ByVal"/>
</method>
</migClass>
|
Note that it must precede the
gmSL statement as the
gmSL code references it. A new section of code can now be added to the transform method that scans forward in the referencing code, removes the old method calling operations and set command code, and replaces it with a reference to the pattern defined above.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
iEnd = sqlString;
opcd = Opcode.GetOperation(codptr,iEnd,subcd);
if(opcd != OPC.CUF) return 0;
iEnd = iEnd + sizeof(OPC.CUF);
opcd = Opcode.GetOperation(codptr,iEnd,subcd);
if(opcd != OPC.REF) return 0;
iEnd = iEnd + sizeof(OPC.REF);
opcd = Opcode.GetOperation(codptr,iEnd,subcd);
if(opcd != OPC.ARG) return 0;
iEnd = iEnd + sizeof(OPC.ARG);
opcd = Opcode.GetOperation(codptr,iEnd,subcd);
if(opcd != OPC.CMD) return 0;
iEnd = iEnd + sizeof(OPC.CMD);
nDelete = iEnd - sqlString - sizeof(OPC.PAT);
createCommand = Symbol.FindIdentifier("RDO.DotNet.CreateCommand");
nCode = Opcode.DeleteCode(sqlString,nCode,nDelete);
Opcode.SetLength(nCode);
Opcode.SetOperation(codptr,sqlString,OPC.PAT,createCommand);
|
Note that the statement that gets the root offset of the new method is highlighted. The comparison log shows that the code acheived the desired result.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
***** rdotran.bnd
(SP = conn.CreateCommand()).CommandText = SQL;
***** RDOTRAN.SAV
SP = conn.CreateCommand("QueryAcct",SQL);
*****
|
The next difference between the unmigrated and migrated code is as follows. This particular difference occurs twice.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Current: SP.Parameters[0].Value = "5005";
Target : SP.Parameters.Add(new System.Data.SqlClient.SqlParameter("@0", null)).Value = "5005";
|
The compiler processes this by generating a generic
COL.Item operation that must be replaced by a pattern string.
Code Block |
---|
language | xml |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
<Method id="Parameters" type="object" migPattern="%1d.Add(new System.Data.SqlClient.SqlParameter(\S@%2d\S, null))" >
<argument id="index" type="Integer" status="ByVal" />
</Method>
|
This pattern is again stored in the migClass
DotNet. The code for the transform then is simply
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
int rdoPreparedStatement_rdoParameters(int subRoot,int icode,int iRefer)
{
int nCode;
tCodeBlock codptr;
int iEnd;
int opcd;
int subc;
int parameters;
nCode = Opcode.GetLength();
codptr = Opcode.GetCode();
iEnd = Opcode.FindArgumentEnd(codptr,iRefer+7,nCode);
opcd = Opcode.GetOperation(codptr,iEnd,subc);
if(opcd != OPC.COL || subc != OPC.COL.Item) return 0;
parameters = Symbol.FindIdentifier("RDO.DotNet.Parameters");
nCode = Opcode.ExpandCode(iEnd,nCode,sizeof(OPC.PAT) - sizeof(OPC.COL));
Opcode.SetLength(nCode);
Opcode.SetOperation(codptr,iEnd,OPC.PAT,parameters);
return iRefer;
}
|
It checks for the needed operation, finds the root of the new pattern variable, replaces the old operation code with the new reference to the pattern and
returns the new code reference scan location. As the following change log shows, both instances of the
rdoParameters where changed.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Comparing files rdotran.bnd and RDOTRAN.SAV
***** rdotran.bnd
SP.Parameters.Add(new System.Data.SqlClient.SqlParameter("@0", null)).Value = "5005";
SP.Parameters.Add(new System.Data.SqlClient.SqlParameter("@1", null)).Value = "Test%";
***** RDOTRAN.SAV
SP.Parameters[0].Value = "5005";
SP.Parameters[1].Value = "Test%";
*****
|
An important difference in the migrated code has to do with the
SqlDataReader variable
Results. In the unmigrated version, it is declared as a method level local variable and is then opened in the code.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
System.Data.SqlClient.SqlDataReader Results = null;
...
Results = SP.ExecuteReader();
|
In the migrated version, its scope is limited, as it is declared and opened in a
using statement.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
using (System.Data.SqlClient.SqlDataReader Results = SP.ExecuteReader())
{
|
An important note here is that the target form of the
OpenResultset has been migrated to
ExecuteReader; however, with the symbol table it still has its source name which must be used. Trying to transform something like
rdoPreparedStatement_ExecuteReader would not work.
The transform method itself begins in the standard way with the required declaractions.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
int rdoPreparedStatement_OpenResultset(int subRoot,int iStart,int iRefer)
{
tCodeBlock codptr;
int nCode;
int opcd;
int subcd;
int icode;
int varRoot;
tVariable varInfo;
|
The first set makes certain that the expected type of reference is present and it obtains the root of the
Results variable.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
codptr = Opcode.GetCode();
nCode = Opcode.GetLength();
icode = iRefer;
opcd = Opcode.GetOperation(codptr,icode,subcd);
if(opcd != OPC.REF) return 0;
icode = icode + sizeof(OPC.REF);
opcd = Opcode.GetOperation(codptr,icode,subcd);
if(opcd != OPC.ARG) return 0;
icode = icode + sizeof(OPC.ARG);
opcd = Opcode.GetOperation(codptr,icode,subcd);
if(opcd != OPC.CMD) return 0;
opcd = Opcode.GetOperation(codptr,iStart,varRoot);
if(opcd != OPC.LDA) return 0;
|
The second step is to set the
DeadCode property of the variable to
True. Doing this blocks the declaration of the variable in the list of local variables.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
varInfo = Store.DeltaVector(varRoot);
varInfo.DeadCode = True;
|
And finally the third step changes the
CMD.Set into an
IFS.Using operation. This operation requires a type as well as a variable reference. The operation
TYV.root displays the type of a root; so it is inserted into the code as well.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Opcode.SetOperation(codptr,icode,OPC.IFS,OPC.IFS.Using);
nCode = Opcode.ExpandCode(icode,nCode,sizeof(OPC.TYV));
Opcode.SetLength(nCode);
Opcode.SetOperation(codptr,icode,OPC.TYV,varRoot);
return iRefer;
}
|
The file comparison shows that the declaration has been removed as well as the following desired change.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Comparing files rdotran.bnd and RDOTRAN.SAV
***** rdotran.bnd
using (System.Data.SqlClient.SqlDataReader Results = SP.ExecuteReader())
{
while (!Results.EOF)
....
***** RDOTRAN.SAV
Results = SP.ExecuteReader();
while (!Results.EOF)
..
*****
|
The using statement enters a new indentation level into the code; therefore, all the statements below it are shifted. In fact the translation log now shows this warning
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
AUTHOR WARNING#02: Indentation level not balanced old = 3 current = 4
in <RDOToNET.execQuery>
|
The end of the using block must be found as well and entered into the migrated code.
Within the unmigrated code the
Openresultset method has a corresponding
Close method. In the migrated approach this method call is replaced by an
IFS.EndUsing operation. The transform code is as follows.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
int rdoPreparedStatement_Close(int subRoot,int icode,int iRefer)
{
int nCode;
tCodeBlock codptr;
iRefer = Opcode.CommentOut(iRefer,OPC.CMT.Delete);
iRefer = iRefer + sizeof(OPC.CMT);
nCode = Opcode.GetLength();
codptr = Opcode.GetCode();
nCode = Opcode.ExpandCode(iRefer,nCode,sizeof(OPC.IFS));
Opcode.SetLength(nCode);
Opcode.SetOperation(codptr,iRefer,OPC.IFS,OPC.IFS.EndUsing);
return iRefer;
}
|
The
Opcode.CommentOut method finds the end of the statement containing the
Close method reference and replaces it with a
CMT.Delete operation which will delete the entire statement from the target code. It returns the code offset of that
CMT operation. The
transpose method then inserts an
IFS.EndUsing operation after the
CMT. This achieves the desired result.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
***** rdotran.bnd
}
***** RDOTRAN.SAV
SP.Close();
*****
|
The next difference is in the while loop that reads the records from the result set.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Current: while (!Results.EOF)
Target : while (Results.Read())
|
In the current unmigrated code the while loop checks for an end-of-file while the target code performs the actual read. Using the types of techniques used earlier, the simple approach is to change the migName of the
EOF to
Read() using
gmPL.
Code Block |
---|
language | xml |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
<property id="EOF" type="Boolean" status="Out" migName="Read()"/>
|
Then the transform method for the property can simply check for the
NOT operation and remove it. This is what the actual reference code looks like.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
266 | 2.266 | 1.261 | Boolean | LLP | Component:EOF:59440
271 | 1.261 | 1.261 | Boolean | MEM | Child
273 | 1.261 | 1.261 | Boolean | NOT | Arithmetic
|
The transpose method then merely checks for the pattern and removes the
NOT if present. The code is straight forward.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
int __rdoResultset_EOF(int subRoot,int iStart,int iRefer)
{
int nCode;
tCodeBlock codptr;
int opcd;
int subcd;
int icode;
nCode = Opcode.GetLength();
codptr = Opcode.GetCode();
icode = iRefer + sizeof(OPC.LLP);
opcd = Opcode.GetOperation(codptr,icode,subcd);
if(opcd != OPC.MEM) return 0;
icode = icode + sizeof(OPC.MEM);
opcd = Opcode.GetOperation(codptr,icode,subcd);
if(opcd != OPC.NOT) return 0;
nCode = Opcode.DeleteCode(icode,nCode,sizeof(OPC.NOT));
Opcode.SetLength(nCode);
return iRefer;
}
|
Checking the change log shows that the combination of the new migName and the removal of the
NOT acheived the correct result.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
***** rdotran.bnd
while (Results.Read())
***** RDOTRAN.SAV
while (!Results.EOF)
*****
|
Migrations that combine noncontingent renaming with contingent code modification are referred to as "shallow" transforms. The technology used by
gmBasic is derived from the field of transformational grammar. The meaning of sentences is referred to as "deep structure", and the representation of the sentence as uttered is referred to as "surface structure". Rules that mix these two levels are called "shallow" and should generally be avoided. In our sample code, the only reference to the "EOF" property is in that while clause where the renaming to "Read()" is valid. But in other contexts, the transform would fail to apply, but the "EOF" would still be changed "Read()" -- certainly causing bad code.
In places such as this, shallow transforms are fine, but beware of them in larger scale migrations where they can introduce problems. A more complex approach would introduce a
DotNet method
Read and then do a contingent replacement.
The next difference is in the while loop that reads the records from the result set.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Current: f = Results.rdoColumns["FirstName"].ToStr();
Target : f = Results["FirstName"].ToStr();
|
This difference involves the references to two properties
rdColumns and to
Value both of which can simply be removed. The actual references can be seen in the code audit.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
280 | | | | LEV | Nest0
282 | 1.282 | 1.282 | RDO.rdoResultset | LDA | Variable:Results:106065
287 | 2.287 | 1.282 | RDO.rdoColumns | LLP | Component:rdoColumns:57254
292 | 1.282 | 1.282 | RDO.rdoColumns | MEM | Child
294 | 1.282 | 1.282 | RDO.rdoColumns | LEV | Nest1
296 | 2.296 | 2.296 | String | LSC | 9:FirstName
301 | 2.296 | 1.282 | RDO.rdoColumns | ARG | String
303 | 1.282 | 1.282 | RDO.rdoColumn | COL | Item
305 | 2.305 | 1.282 | Variant | LLP | Component:Value:54311
310 | 1.282 | 1.282 | Variant | MEM | Child
|
Except for the names of the methods they are identical.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
int __rdoColumn_Value(int subRoot,int icode,int iRefer)
{
int nCode;
tCodeBlock codptr;
nCode = Opcode.GetLength();
codptr = Opcode.GetCode();
nCode = Opcode.DeleteCode(iRefer,nCode,sizeof(OPC.LLP) + sizeof(OPC.MEM));
Opcode.SetLength(nCode);
return iRefer;
}
int __rdoResultset_rdoColumns(int subRoot,int icode,int iRefer)
{
int nCode;
tCodeBlock codptr;
nCode = Opcode.GetLength();
codptr = Opcode.GetCode();
nCode = Opcode.DeleteCode(iRefer,nCode,sizeof(OPC.LLP) + sizeof(OPC.MEM));
Opcode.SetLength(nCode);
return iRefer;
}
|
The change log shows that these changes produced the desired result.
Code Block |
---|
language | none |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
Comparing files rdotran.bnd and RDOTRAN.SAV
***** rdotran.bnd
f = Results["FirstName"].ToStr();
writeLog(f);
f = Results["eMail"].ToStr();
***** RDOTRAN.SAV
f = Results.rdoColumns["FirstName"].Value.ToStr();
writeLog(f);
f = Results.rdoColumns["eMail"].Value.ToStr();
*****
|
The RDO._rdoResultset_MoveNext code
The final difference between the two translations is that the
MoveNext call is no longer needed. The migrated code already does the read in the while loop. The transform method can simply comment out the unwanted statement.
Code Block |
---|
language | cpp |
---|
theme | Eclipse |
---|
linenumbers | true |
---|
|
int __rdoResultset_MoveNext(int subRoot,int icode,int iRefer)
{
iRefer = Opcode.CommentOut(iRefer,OPC.CMT.Delete);
return iRefer;
}
|
This demonstration migration has now been completed,