Named arguments for ABL procedures.

Allan Green

New Member
Hi everyone,

I've been playing with the ABL preprocessor...a scary place indeed. I suspect from the relative lack of discussion of it online that it isn't terribly widely used. I think I've mastered supplying arguments (both numbered and named) to include files, which is great. However I've only been able to get numbered arguments to work in procedure (.p) files. (I've not been able to find anything in the documentation or online to confirm that this is expected behaviour though...)

I'll settle for numbered arguments if I have to, but named arguments are inherently self-documenting and less likely to be mislabelled as a consequence of careless future revisions.

(PS I've found the documentation of procedure arguments pretty limited by Progress standards. And I'm sure that one or two of the (Progress) pages I did find confuse arguments and parameters which is a little worrying....)

(Just to invite the usual well-meaning advice (and abuse :)), I'm using OE 11.1.)

Thanks for reading and any comments you may have.

Allan Green

-------------------------

Here are my latest attempts to get this working just so you can see what I am trying to do!
Code:
/*    Name: forum_submission_parent.p

      Description: This module sets up the values of named arguments
                "argument1" and "argument2" passed to
                forum_submission_daughter.p
*/

&SCOPED-DEFINE cArgument "Teststring"
&SCOPED-DEFINE iArgument 123

RUN forum_submission_daughter.p {
   &cArgument="Teststring"
   &iArgument = 123
   }.

RETURN.

----------------------

/*     Name: forum_submission_daughter.p

       Description: This module was written to clarifiy whether it is
                 possible to use named arguments within an OpenEdge
                 external procedure file.
*/

DEFINE VARIABLE cLocalExample AS CHARACTER FORMAT "x(12)".
DEFINE VARIABLE iLocalExample AS INTEGER FORMAT ">>>9".

cLocalExample = {&cArgument}.
iLocalExample = {&iArgument}.

DISPLAY cLocalExample
        iLocalExample
        WITH FRAME fNamedArgsIncFile
             ROW 5 COLUMN 5
             TITLE "Procedure file daughter".

RETURN.
 
Last edited by a moderator:
(Apologies, just noticed that the variable names I used in the parent routine don't match the commenting. And I cannot find a way to edit them...)
 
Sorry this is not what you want - but please try and avoid using preprocessors - for your sake and the poor bastards that will have to maintain the code after you.

A long long time ago when the ABL was the 4GL and we didn't have parameters or persistent procedures (v3-v5 I seem to remember) there was a need for include files and preprocessing. I amy be in a minority but feel that include files should only now be used for static temp-table / dataset definitions and preprocessors used only for very specific os-related things (dll calls etc)

IMHO The combination of includes and preprocessors creates a soup of horrible, opaque code - and also is a swine to debug because the line number in the log doesn't match anything at all in the source, so you then have to generate preprocessed files etc.

“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live”​

― John Woods

good words to live by :)
 
I am not quite so opposed to preprocessors and includes as Julian is, but you should stay well away from using arguments for procedures. They mean that you cannot run on r-code, only source (and are the underlying reason why the data dictionary requires a development license). Please don't go there - use parameters instead.
 
You are looking for (although you really should not) RUN statement arguments.

argument
A constant, field name, variable name, or expression that you want to pass as a compile-time argument to the external procedure you are running.

When you pass arguments to an external procedure, the AVM converts those arguments to character format. ABL recompiles the called procedure, substitutes arguments, and then runs the procedure. You cannot precompile a procedure to which you pass arguments. (If you use shared variables instead of arguments, the procedure can be precompiled. This yields more efficient code.)

Note: You cannot pass compile-time arguments in a call to an internal procedure.

You simply add the arguments after the run. The curly braces you were using will try to include something.

Code:
/* parent.p */

run daughter.p 'Teststring' 123.

They are number only, by the looks of things.

Code:
/* child.p */

DEFINE VARIABLE carg AS CHARACTER FORMAT "x(12)".
DEFINE VARIABLE iarg AS INTEGER FORMAT ">>>9".

carg = '{1}'.
iarg = {2}.

DISPLAY 
   carg
   iarg
   WITH FRAME fNamedArgsIncFile
   ROW 5 COLUMN 5
   title 'daughter'
   .

11.1 has class objects, no need for the above antiques.
 
IMHO The combination of includes and preprocessors creates a soup of horrible, opaque code - and also is a swine to debug because the line number in the log doesn't match anything at all in the source, so you then have to generate preprocessed files etc.

Even without preprocessor magic, a single include will skew the debug listing line numbers.

When using PDSOE, you can use ALT+L to go to the debug listing line number.
Our build pipeline also stores the debug listings (produced by PCT) of our release builds in case version x.y.z threw some error.
 
When using PDSOE, you can use ALT+L to go to the debug listing line number.

FWIW ALT-L also works in the Riverside VSCode extension.

Even without preprocessor magic, a single include will skew the debug listing line numbers.

And it is *extremely* hard to avoid include files when you want to use a temp-table definition in more than one program. IMO the price you pay is significantly less than the inevitable errors introduced when you need to edit the TT definition.
 
I was puzzledly looking at your code, wondering how it could possibly run and - if it would run - what it should do.
Still not convinced that I know what you are trying to achieve here.

The example does not compile in 11.7, let alone run. I think you are trying to use run-time parameters.
If you do: don't. Stay away from run-time parameters (not to be confused with regular, normal parameters) and includes (except for tt-definitions and perhaps one or two other exceptions). Using includes for code was useful in v6 and earlier, but not now anymore. I am fully team @jmls here

With run-time parameters I mean this:
Code:
RUN c:\temp\test.p "Teststring" 123

// c:\temp\test.p
MESSAGE "{1}" "{2}"
  VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.

This is wrong on so many levels. To stay away from psychopaths, this is more safe:
Code:
RUN c:\temp\test.p("Teststring",123).

// c:\temp\test.p
DEFINE INPUT PARAMETER pcParam AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER piParam AS CHARACTER NO-UNDO.

MESSAGE pcParam piParam
  VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
note: changed the code to reflect OP's problem
 
Last edited:
And to add to the documentation part @Stefan posted: it suggests to use shared variables instead for more efficient code.
For this the same advice applies, namely: DON'T!

Shared variables are evil, just as includes with code and the release statement.
They all might have their place, but as far as I'm concerned, that place is hell.
 
Echoing the horror of others, one really needs to be clear about what one needs and wants at run time versus what one needs and wants at compile time. If all you are doing is displaying a value, then you need nothing more complicated than a run time parameter to pass in the value. Oh, and check out ABL OO for ways to encapsulate logic so that you don't have dangling dependencies.
 
I was puzzledly looking at your code, wondering how it could possibly run and - if it would run - what it should do.
Still not convinced that I know what you are trying to achieve here.

The example does not compile in 11.7, let alone run. I think you are trying to use run-time parameters.
If you do: don't. Stay away from run-time parameters (not to be confused with regular, normal parameters) and includes (except for tt-definitions and perhaps one or two other exceptions). Using includes for code was useful in v6 and earlier, but not now anymore. I am fully team @jmls here

With run-time parameters I mean this:
Code:
RUN c:\temp\test.p "Teststring" 123

// c:\temp\test.p
MESSAGE "{1}" "{2}"
  VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.

This is wrong on so many levels. To stay away from psychopaths, this is more safe:
Code:
RUN c:\temp\test.p("Teststring",123).

// c:\temp\test.p
DEFINE INPUT PARAMETER pcParam AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER piParam AS CHARACTER NO-UNDO.

MESSAGE pcParam piParam
  VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
note: changed the code to reflect OP's problem

Hi, indeed the code I submitted does not compile... but I could not see why not :)... It was written to demonstrate a problem with a rather larger module which I was testing as part of the development of our OE application, and contains just about the minimum code with which I could illustrate the issue. I've made a fool of myself often enough here not to want to complain about OE without being reasonably sure of myself and I really could not find anywhere that named arguments are restricted to include files.
 
Echoing the horror of others, one really needs to be clear about what one needs and wants at run time versus what one needs and wants at compile time. If all you are doing is displaying a value, then you need nothing more complicated than a run time parameter to pass in the value. Oh, and check out ABL OO for ways to encapsulate logic so that you don't have dangling dependencies.
Thanks Tamhas... I've never before stooped to run-time arguments but on this occasion I wanted to avoid having to code (and support) three very similar modules when a couple of runtime arguments would have allowed me to reduce the coding to one. (Actually I have ended up wasting a lot more time trying to get named arguments to work in procedure files than ever it would have done to code the three near-duplicate modules...)
 
You are looking for (although you really should not) RUN statement arguments.



You simply add the arguments after the run. The curly braces you were using will try to include something.

Code:
/* parent.p */

run daughter.p 'Teststring' 123.

They are number only, by the looks of things.

Code:
/* child.p */

DEFINE VARIABLE carg AS CHARACTER FORMAT "x(12)".
DEFINE VARIABLE iarg AS INTEGER FORMAT ">>>9".

carg = '{1}'.
iarg = {2}.

DISPLAY
   carg
   iarg
   WITH FRAME fNamedArgsIncFile
   ROW 5 COLUMN 5
   title 'daughter'
   .

11.1 has class objects, no need for the above antiques.

Hi Stefan,

> They are number only, by the looks of things.

If only the OE documentation had actually said as much ...

I could make it all work with numbered arguments but I don't want to ever debug the consequences of introducing a new out-of-order argument in future... After the comments here today I am not going to run-time compilation after all...

BW Allan.
 
Sorry this is not what you want - but please try and avoid using preprocessors - for your sake and the poor bastards that will have to maintain the code after you.

A long long time ago when the ABL was the 4GL and we didn't have parameters or persistent procedures (v3-v5 I seem to remember) there was a need for include files and preprocessing. I amy be in a minority but feel that include files should only now be used for static temp-table / dataset definitions and preprocessors used only for very specific os-related things (dll calls etc)

IMHO The combination of includes and preprocessors creates a soup of horrible, opaque code - and also is a swine to debug because the line number in the log doesn't match anything at all in the source, so you then have to generate preprocessed files etc.

“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live”​

― John Woods

good words to live by :)

Love this quote! But maybe the reason I wanted to use the preprocessor is that the psycopath is actually *ME"!!! (Don't worry, your combined replies have convinced me otherwise...)
(Actually I have always shared TEMP-TABLE schemas as include files though where I got this idea from has long been forgotten...)
 
Hi everyone who took the trouble to reply and thank you.

I will take your collective advice and steer clear of the preprocessor in future. (Well, TEMP-TABLE definitions in include files excepted, anyway...)

Best wishes,

Allan
 
This abomination will give you named run-time arguments :-)

Code:
/* parent.p */

RUN child.p 'cArg' 'Teststring' 'iArg' 123.

Code:
/* child.p */

&if '{1}' = 'cArg' &then
   &scop cArg '{2}'   
&elseif '{3}' = 'cArg' &then
   &scop cArg '{4}'
&endif

&if '{1}' = 'iArg' &then
   &scop iArg {3}
&elseif '{3}' = 'iArg' &then
   &scop iArg {4}
&endif

def var carg as char format 'x(12)'.
def var iarg as int  format '>>>9'.

carg = {&cArg}.
iarg = {&iArg}.

display
   carg
   iarg
  with
   frame fNamedArgsIncFile
   row 5 column 5
   title 'child'
   .

Feel free to create a little preprocessor engine to handle any number of named parameters, I wouldn't. :)
 
This abomination will give you named run-time arguments :-)

Code:
/* parent.p */

RUN child.p 'cArg' 'Teststring' 'iArg' 123.

Code:
/* child.p */

&if '{1}' = 'cArg' &then
   &scop cArg '{2}'  
&elseif '{3}' = 'cArg' &then
   &scop cArg '{4}'
&endif

&if '{1}' = 'iArg' &then
   &scop iArg {3}
&elseif '{3}' = 'iArg' &then
   &scop iArg {4}
&endif

def var carg as char format 'x(12)'.
def var iarg as int  format '>>>9'.

carg = {&cArg}.
iarg = {&iArg}.

display
   carg
   iarg
  with
   frame fNamedArgsIncFile
   row 5 column 5
   title 'child'
   .

Feel free to create a little preprocessor engine to handle any number of named parameters, I wouldn't. :)
Thank you Stefan. I am so impressed that you've managed to come up with a workaround for the glaring omission in OE of named arguments for procedure files. In light of everyone's comments however I may not adopt this for the time being.... :)
 
Lest anyone think that I am on vacation...

OpenEdge 11.1 is ancient, obsolete, and unsupported. You should upgrade.

All of OE11 is now ancient & obsolete but 11.7 is a much, much better place to be than 11.1.

Since you speak of run time arguments you apparently have source code and a compiler license. So there is really nothing technical stopping you from getting to OE12.8.

OE11.1 embodies a number of really annoying bugs. I have far too many version checks and pre-processors in ProTop that exist solely to deal with 11.1's deficiencies. It is a release that I wish never happened. Nearly as troublesome as 7.3c01.
 
Echoing the horror of others, one really needs to be clear about what one needs and wants at run time versus what one needs and wants at compile time. If all you are doing is displaying a value, then you need nothing more complicated than a run time parameter to pass in the value. Oh, and check out ABL OO for ways to encapsulate logic so that you don't have dangling dependencies.
I knew I'd find a post from you Thomas! I was here to bang that drum. Even with "simpler" OO concepts like overloading there's a great deal you can do to make reusable and maintainable code.
 
Back
Top