Do2Class utility changes all DO ROUTINE code to call CLASS Methods in a Procedure. The code changes are minor, but to make the changes manually is tedious work, hence the utility to do it right with little work. I built it to be simple and reliable. I make simple changes to the TXA (no moving blocks) so that a compare of TXA file changes is easy to follow. I use it myself on large procedures. I converted KSS and put up a KSS Do2Class Repo on Guthub.
Do NOT be put off by the amount I have written below. This is simple, works and any problems discussed below insert tokens like '? Return was in Routine ?'
into the converted source that cause compiler errors so you cannot miss them. The only thing that can cause bugs are implicit variables passed between procedure and routine.
The major benefit of the change is Methods allow passing Parameters and can have a Return Value e.g. NyClass.IsFormOK PROCEDURE(BYTE Force),BOOL.
While there is an extra step of declaring each Method (Procedure) in a data embed CLASS declaration, that provides sort a "Table of Contents" to the local routines that were written for a specific procedure and documents the parameters and returns. With routines to pass and return you must use local variables and that is NOT solid code.
The Class name I prefer to use to replace routines is DOO CLASS. I am used to typing DO
so that makes it easy and I like that it implies it replaces DO
. You can use any name you like. If you plan to extract the class for use elsewhere then give it a logical name like "ParserClass" and also check the SELF option.
Watch the June 19, 2020 presentation on Clarion Live or on YouTube Do2Class - Easy change from Routine to Class. You should also watch 237 and 239 "Lisa's First Class" where most everything done manually is done by Do2Class.
The process for converting in short is Export one Procedure to TXA, Load TXA in Do2Class, Save Do2Class.TXA, Import Do2Class.TXA to APP, Build, Review. To undo the changes import the original TXA. Compare the before and after CLW files you can review exactly what was changed. You can also compare TXA files.
- Backup your APP, CwProj, SLN, CLW files for restore. Also for CLW comparison
- Clarion: Export One Procedure to TXA and name it APP_ProcName.TXA
- Do2Class: Load above TXA
- Do2Class: Review the tabs Steps, Omit, Problems, Implicits
- Do2Class: Save TXA it will be named App_ProcName_Do2Class_.TXA
- Clarion: Import App_ProcName_Do2Class_.TXA
- Clarion: Build - Review errors and fix - Run
- To understand the process compare CLWs to see exact changes (eventually skip). A quick check of the CLW is after conversion the line count should have increased 1x - 2x the routine count. Each gets 1 new line to declare the method, plus 1 CODE line, minus 1 line if it had DATA.
- Undo the changes by simply importing the original TXA export APP_ProcName.TXA
This method will also work with module TXA files as long as they only contain one procedure. It will work with APV files used for version control so it will work with @Rick-UpperPark Upper Park integration tool.
RouName ROUTINE
changes toDOO.RouName PROCEDURE()
DATA
is removed as only for a RoutineCODE
is added as needed for ProcedureEXIT
is changed toRETURN
- Caller
DO RouName
is changed toDOO.RouName()
- Data Declaration is added for
DOO CLASS
- Data Declaration is added
RouName PROCEDURE()
for each routines - Problem code gets
? message ?
which will error and force review
Example of Code Before conversion:
DO Defaults ! Call Setup Default Values
Defaults ROUTINE ! Setup Default Values
Country='USA'
EXIT
Example of Code After Do2Class:
DOO CLASS ! Declaration
Defaults PROCEDURE() ! Setup Default Values
END
DOO.Defaults() ! Call Setup Default Values
DOO.Defaults PROCEDURE() ! Setup Default Values
CODE
Country='USA'
RETURN
Tab | Option | Description |
---|---|---|
TXA | Load TXA Tab | |
SELF | Check to use SELF.Method inside Class method code instead of DOO.Method. If you plan to extract this class and inherit it then check SELF is proper OOP. To replace ROUTINEs I prefer to NOT use SELF and always type DOO. |
|
Do NOT move %Routine to %LocalProcedures |
Methods belong in the %LocalProcedure embed. Uncheck may result in less changes to compare TXA or CLW. |
|
%LocalProcedures Embeds | Local Procedures can contain Local ROUTINEs which should NOT be processed by Do2Class. If the Procedure incorrectly put its ROUTINEs into this Embed then check this box to process it. |
|
DATA Embeds | Check to process Data EMBEDs. Data should NOT have code to change so are skipped. |
|
Method Code | Process Method Code embeds. I doubt it will work. | |
Save | CLASS Code (Save TXA) Tab | |
Omit ! DATA | Uncheck to retain Routine DATA lines as a !DATA comment to allow easily searching for Routines that had DATA to review if it should be encapsulated as Class Data. |
|
Write CLASS to %Data | Check to Write CLASS declaration and Methods to first Data embed. | |
Sort | Check to Sort Methods by Name in CLASS declaration, else in Line Order. | |
Tag Implicit !#$" | Tag Implicit Lines !#$" in the save TXA to make it easy to find them and fix. |
Double click on any LIST line tries to take you to a logical place, e.g. double click on a line on the Problem, Implicit or Omit tab takes you to the view the complete source. Right click on any LIST shows a POPUP of all the other lists that lets you jump to that line in the selected list e.g. on a Problem Right click and on the Popup select TXA to see that line in the TXA file.
XRef tab shows Callee-Caller and Caller-Callee Cross Reference. See screen capture at the end.
Certain code can cause issues when changed from a Routine to a Class Method. Do2Class will identify the important ones on the Problems tab. It also has Implicit variable potential problems for review. Other problems are caught by the compiler. Tokens like '? Return was in Routine ?'
are inserted into the converted source that cause compiler errors so you cannot miss code that needs refactor.
If a Procedure defines a variable name and that same variable name is also defined in a Routine or Class Method then you have a "Duplicate Symbol". The variables can have different types, they will use their locally defined type because it is a local variable. In a Routine the duplicate does NOT cause a warning, but once changed to a Method the compiler will throw a warning. The code works the same.
There is nothing that must be fixed, but it is usually best to refactor the code to NOT have a duplicate by renaming the variable in the Method code or ... This may have revealed a subtle bug in your program. The local duplicate variable is an independent variable. If the routine was relying on referencing the procedure variable that was NOT happening. You may cause a subtle bug (a regression) if you simply remove the local declaration because the method will then be changing the Procedure's variable and that may have not been intended, it may be corrupting the procedures variable.
For Example, in my tests I found KSS had defined Save_Record STRING(SIZE(Pre:Record))
in a Procedure and also a Routine. Once changed to a Method that caused a compiler duplicate warning. In my video I just commended out the Method declaration to remove the warning. BUT that would cause a subtle bug (regression) because now the Method would change the Procedure's Save_Record variable. I should have renamed the variable in the Method data and code. Renaming in the Procedure also would assure I changed all of them or I would get errors.
MyReport PROCEDURE()
Ndx LONG !<- Proc Variable
ScanQ ROUTINE
DATA
Ndx USHORT !<- No Warning
MyClass.ScanQ PROCEDURE()
Ndx SHORT !<- Warning -
CODE
A RETURN
in a Routine is from the Procedure but when changed to a Class it will be Method Return not Procedure. You must refactor the calling and new Class code. The Do2Class TXA code will include a line in the Method before the RETURN
with ? Return was in Routine ?
that will cause an obvious compile error so you cannot miss fixing the code.
This problem will never occur in ABC except in a Source template. In Legacy templates it is unlikely to slip by because the common code DO ProcedureReturn
will error when moved to a Method.
DO SelectLabelPaper !Note routine might procedure return
SelectLabelPaper ROUTINE
Request = SelectRecord
BrowseLabels()
IF Response = Cancelled
DO ProcedureReturn !<- Legacy code in a Class Method get Compiler Error
POPBIND ; RETURN !<- No Compiler Error, just returns from Method
END
One possible refactor is to change the new Method to return a value. Have False to indicate to the caller to Return.
SelectLabelPaper PROCEDURE(),BOOL !Returns False if no Label so must exit
IF DOO.SelectLabelPaper()=False THEN
POPBIND ; RETURN
END
DOO.SelectLabelPaper PROCEDURE() !,BOOL
CODE
Request = SelectRecord
BrowseLabels()
? Return was in Routine ? <-- Do2Class inserted this to force error so cannot miss refactoring
RETURN CHOOSE(Response <> Cancelled) !Tell caller False if cancelled
An IF OMITTED()
in a routine is testing for a Procedure(<Parameter>)
omitted. When moved to a Class the Omitted() it will be for a Method Parameter. Since the Method will not have that () the compiler will use the value of the Parameter variable and the code will NOT work right. The code must be refactored to move the Omitted() check outside of the Method. One way is to change that Method back to a Routine. An OmittedParm.TPL template is provided that generates OMITTED_Variable's for each and the code to set them OMITTED_Variable=OMITTED(Variable)
right after the CODE statement. This problem is most likely to occur in ABC where a Routine is required to "see" omitted.
The Do2Class TXA code will include a line in the Method with ? Omitted() was in Routine ?
that will cause an obvious compile error so you cannot miss fixing the code.
PrintMyReport PROCEDURE(<BYTE _Parm1_>)
SelectLabel ROUTINE
CODE
IF OMITTED(_Parm1_) THEN
!This will work correctly
END
MyClass.SelectLabel PROCEDURE() !Routine change to Class Method
CODE
? Omitted() was in Routine ? <-- Do2Class inserted this to force error so cannot miss refactoring
IF OMITTED(_Parm1_) THEN !No Compiler Error
!Will NOT work as expected
!_Parm1_ treated as "Expression" i.e. value
END
Implicit variables can cause regressions in this conversion if they are used to "pass parameters" or the code relies on their value persisting between calls.
Best move is don’t use Implicit Variables ever, or beyond a few lines of code. Read the help on "Implicit Variables" and note it says
Therefore, implicit variables should be used with care and caution, and only within a limited scope (or not at all).
An Implicit value set in the Procedure then used in a Routine (i.e. like a parameter) will work and the value persists. When changed to a Class Method the Implicit will be a independent local variable for that Method and the code will NOT work like the Routine. Implicits in Routines is discussed by the help and the scope and persistence varies based if the procedure also defined it and a pragma, so best move is do not use implicits.
SayHi# = True
DO ShowHelloWorld
MyClass.HelloWorld()
ShowHelloWorld ROUTINE
IF SayHi# THEN Message('You will see this').
MyClass.HelloWorld PROCEDURE()
CODE
IF SayHi# THEN | ! SayHi# is Local so will =0 !#$" <- Do2Class optional Token
Message('NEVER See Hello SayHi#=' & SayHi#). !#$" <- Do2Class optional Token
Do2Class has an Implicit tab to show you all use of implicit variables. You can double click or right click on the line to see the complete code. On the Save TXA tab is a Check option to "Tag Implicit Code with !#$"" that will add that comment token !#$"
to all lines that use implicits. This will make it easy to search for them and refactor the code.
As the conversion is done an XRef is built showing Callee-Caller and Caller-Callee. This can help understand the code and refactor it. Even if you do not convert you may find the Xref helpful. References to *PROCEDURE* are for the main procedure calling the routines. You can double click on these lines and just to the source code, or right click on them and a popup menu offers to jump to that line in any of the