Include C Utilities in Your Developer Library: Evaluating a Mathematical String Expression
March 24, 2010 Michael Sansoterra
Most i programmers have a library of RPG or COBOL utilities they’ve written or filched from another source. Having a library of samples and pre-built code is a great productivity booster. However, have you ever considered adding C and C++ code to your library? You don’t know C? Big deal. If you have the C compiler loaded on your i, there’s an abundance of C and C++ code out there on the internet just waiting for you to discover. Plus, if you’re well versed in ILE concepts, you may be further along the C path than you think. Consider this scenario: every once in a while I find myself working on a project that requires the run-time evaluation of a mathematical expression contained in a string. Writing a versatile mathematical expression evaluator in RPG would be a huge chore. A while back, I published a tip on how to use SQL as a dynamic expression evaluator. While SQL can evaluate a string expression dynamically, it is relatively slow, so for this one particular project I found myself looking for a faster solution. I hopped online and found a program called eval.c (from a Web site called The SNIPPETS C Source Code Archives), which is written in C. It was a great solution that has served its purpose well. For a little more background, this one particular project involved executing a large number of calculations based on formulas that were constantly subject to change by the users. My desire was to remove these hard-coded formulas from the RPG program and soft code them so that developers wouldn’t have to spend a lot of time changing source, compiling, and testing. For the purpose of this discussion, a formula could be as simple as: “QTY * UNITPRICE”. At run time I would substitute numerical values in place of the variable names (for example, “8 * 7.50”) and then evaluate and store the results. Once I had the C code compiled, it could be invoked using a prototype in RPG like this: MyResult = Eval('8 * 7.50') In SQL, the C code can also be invoked as a user-defined function: Select Eval('8 * 7.50') As MyResult From SysIBM.SysDummy1 Of course, both of these calls return the result of 60. If you’d like to know exactly how this code was compiled and accessed by SQL and RPG, see the section titled “Instructions: How To Create the “eval.c” Program on the System i/iSeries/AS/400/I” at the end of this tip. Since many of us IBM “i” folk don’t know C (or don’t know it very well–I last did C in the late ’80s on my Commodore 128), here are a few notes to get you started:
eval MyString:s Because I don’t know C very well, I frequently have to review a few concepts from one of the many free C, C++ tutorials on the Web. As already mentioned, it is often necessary to write a little additional “wrapper” code that allows the C routines to be accessed from RPG or SQL. Here are a few of the C reference sites I found useful:
Also, since you’ll likely be using the C/C++ code you download from an RPG program, you’ll need to know how to access the C code from within RPG. To do this, you’ll need to create a prototype in RPG to call the C program or function. This means you’ll have to know how to deal with pointers and translate data type differences between the two languages. There are already many good tips on how to prototype C calls in RPG on the Internet. IBM’s Barbara Morris wrote one such article, which you can see here. In a prior tip, I exhorted DB2 SQL developers to find existing SQL code from other database platforms and convert it to DB2 where needed in order to save time and leverage someone else’s expertise. This tip is urging the same concept–expand your horizons to include C. When code samples aren’t available in your “native” programming languages, expanding your search to include other languages can pay big dividends. While you may stumble a bit getting out of the gate, the time investment will be worth it. Leverage existing code, get things done quicker (and often better) and be more productive. In this economy, increased productivity is golden–you’re bound to get a raise (or at least a little more appreciation)! Instructions: How To Create the “eval.c” Program on the System i/iSeries/AS/400/i Step 1: After identifying the source code I needed, I downloaded the following nine files and placed them (both C source and include (“copy”) source files) on the IFS in the folder /tmp/c_files.
For simplicity, the compilation commands assume all source and include files are placed in the /tmp/c_files folder, although normally these would be placed in source members or, if your organization’s policy allows, in a folder on the IFS intended for storing source. Step 2: Compile the supporting modules that eval.c requires. CRTCMOD MODULE(XXXXX/RMALLWS) SRCSTMF('/tmp/c_files/rmallws.c') OUTPUT(*PRINT) CRTCMOD MODULE(XXXXX/STRUPR) SRCSTMF('/tmp/c_files/strupr.c') OUTPUT(*PRINT For the record, it was trial and error that revealed I had to do this. I originally tried to compile the eval.c program and found I had some unresolved external reference errors that forced me to go back and look for more source and additional instructions. However, already being comfortable with RPG modules and service programs, I had little trouble working through these same issues in a different language. Step 3: I wasn’t happy with how an ILE program or SQL function would interact with this program as coded by the original author (because his function returned its result as an output parameter), so I decided to add a function of my own to return the result to ILE programs and SQL (as a scalar function). Edit the eval.c source file (or source member) and append the following “eval” function to the end: double eval(char *formula) { int retval; double val; retval = evaluate(formula, &val); return val; } This function will now make it easy to access the evaluator’s logic from both ILE and DB2 for i SQL environments. Keep in mind that the program will be named EVAL and the function within the program will also be called eval (lower case). Step 4: Now that the source code is changed and ready to compile, it’s time to create the program. Because the eval function is the only function that will be accessed from other programs, I chose to create it as a service program. Compile the eval.c program as a module: CRTCMOD MODULE(XXXXX/EVAL) SRCSTMF('/tmp/c_files/eval.c') OUTPUT(*PRINT) Create a service program based on the eval module and the two modules that were previously compiled: CRTSRVPGM SRVPGM(XXXXX/EVAL) MODULE(EVAL RMALLWS STRUPR) EXPORT(*ALL) TEXT('Eval Math Expression') The service program will simply be called EVAL and the function to be accessed by the outside world is called eval (in lowercase). Remember, in C the function names are case sensitive. In this case I didn’t use binding source but rather chose the EXPORT(*ALL) option. In most cases, you’ll want to use binding source to specify your own signature for the service program. If you’re acquainted with the concept of ILE modules, programs, and service programs in a language like RPG, then you already understand how they work in the C world. Step 5: If necessary, add your newly created EVAL service program to a binding directory of your choice. ADDBNDDIRE BNDDIR(XXXXX/MYBNDDIR) OBJ((XXXXX/EVAL)) Step 6: The eval function can be accessed by RPG using the following prototype: DEval PR 8F ExtProc('eval') D Formula * Value Options(*String) Here is a complete RPG program showing the use of eval: HDftActGrp(*No) ActGrp(*Caller) BndDir('XXXXX/MYBNDDIR') DEval PR 8F ExtProc('eval') D Formula * Value Options(*String) DTestData S 35 /Free TestData=%Char(Eval('34 + 7 * 2')); Dsply TestData '' *InLR; Return; /End-Free Step 7: If you want to use this C function as an SQL user-defined function, simply register the function in DB2 with the following CREATE FUNCTION statement: Create Function XXXXX/EVAL(Formula VarChar(64)) Returns Double Language C External Name 'XXXXX/EVAL(eval)' Parameter Style General No SQL Deterministic Returns Null On Null Input Not Fenced Notice the external name specified includes the service program name (EVAL) and the function name to be invoked (eval) within parenthesis. Don’t forget that function names are case sensitive. Exported RPG subprocedure names are always converted to uppercase, but this consistency will not be present with C. Michael Sansoterra is a DBA for Broadway Systems in Grand Rapids, Michigan. Send your questions or comments for Mike via the IT Jungle Contact page. RELATED STORIES SQL Cross Platform Interoperability: The Proper Function Executing Dynamic Calculations with Embedded SQL
|
The Snippet site in the article is defunct. They are preserved on GitHub: https://github.com/vonj/snippets