Scripting Languages and COBOL
Authored by and published on behalf of Stephen Gennard.
The use of scripting languages with other languages has increased over the last couple of years, from a simple case of interoperability, reuse of scripting code,
to allowing your code to be customised via the use of external scripts. All of which are real world examples I have seen customers use.
Interoperability between languages is very important to COBOL environments just as much as other languages. Some platforms such as Microsoft's .NET with their
CLR makes life much easier by allowing all languages to share a common infrastructure; i.e., the instruction set and the VM (MSIL and CLR) along with a base class library to get you started.
Environments such as Sun's VM (JVM) provide two different approaches to interoperability with Java, the first is via JNI/JNA and the second is producing bytecode that runs as-is on the VM.
Although the Micro Focus COBOL compiler does not support JVM byte code or Java source generation, it does have support for invoking classes/methods via the OO invoke verb.
This mechanism is very simple to use, you just need to let our object COBOL runtime know the class is a Java class. Which can be done by placing
$JAVA$
before the name of the class and ensuring the class itself can be found by JVM usually by adding extra directories or .jar files
to the CLASSPATH
environment variable.
With Java 6.0 and JSR 223, support for Java based scripting languages were provided via the package javax.script
.
Java has a wealth of scripting languages from AWK to XSLT. My favourites being JPython,
JRuby, and JavaScript.
The java.net website has a comprehensive list of scripting languages - https://scripting.dev.java.net/.
To use the scripting packages, you first need to create a ScriptEngineManager
, then use this to create a specific
ScriptEngine
object for your chosen scripting language and use it.
For example:
- Create a
ScriptEngineManager
object. - Retrieve a
ScriptEngine
object from the manager. - Evaluate a script using the
ScriptEngine
object.
In COBOL, this is quite simply:
$set ooctrl(+p) ooctrl(-f)
class-control.
cls-Script-EngineManager is
class "$JAVA$javax.script.ScriptEngineManager"
cls-Script-Engine is
class "$JAVA$javax.script.ScriptEngine"
cls-object is
class "$JAVA$java.lang.Object"
cls-System is
class "$JAVA$java.lang.System"
cls-PrintStream is
class "$JAVA$java.io.PrintStream"
.
working-storage section.
01 ws-obj-sem object reference cls-Script-EngineManager.
01 ws-javascript object reference cls-Script-Engine.
01 ws-obj object reference cls-object.
01 ws-pout object reference cls-PrintStream.
procedure division.
invoke cls-Script-EngineManager "new"
returning ws-obj-sem
end-invoke
invoke ws-obj-sem "getEngineByName" using
"JavaScript" returning ws-javascript
end-invoke
invoke ws-javascript "eval" using
z"print('Hello, world!')"
returning ws-obj
end-invoke
if ws-obj not equal null
invoke cls-System "getout" returning ws-pout
invoke ws-pout "println" using ws-obj
invoke ws-pout "finalize" returning ws-pout
invoke ws-obj "finalize" returning ws-obj
end-if
$if NO-FINALIZE not defined
invoke ws-obj-sem "finalize" returning ws-obj-sem
invoke ws-javascript "finalize" returning ws-javascript
$end
stop run.
The actual JavaScript being executed is contained in the invoke
statement, which is simply:
print('Hello, world!')
To use the above example, we first need to compile the code and run it.. which is done as follows:
C:\jscripting\HelloWorld>cobol cbljscript.cbl int();
Micro Focus Net Express V5
Version 6.0.00059 Copyright (C) 1984-2009 Micro Focus (IP) Limited.
URN AXCGG/AA0/00000
* Checking complete with no errors - starting code generation
* Generating cbljscript
* Data: 848 Code: 1992 Literals: 904
C:\jscripting\HelloWorld>runm cbljscript
Micro Focus Net Express V6.0.00059
RUN TIME ENVIRONMENT Copyright (C) 1984-2009 Micro Focus (IP) Limited.
URN AXCGG/AA0/00000
Hello, world!
This is just the start, the next piece that is required with interoperability to another language is the ability to pass parameters in and out of the script.
Luckily for us, the clever chaps on the JSR group have provided 'put
' and 'get
' methods that allow us to simply put a name parameter
and get the resulting updated or new parameter.
So consider the example where we need to setup a parameter called 'message
' for the script and then read a parameter called 'replyMessage
' after
the script has been executed. The JavaScript to do this is:
if (typeof(message) == 'undefined')
{
message = "ERROR - 'message' has not been setup"
}
println(message)
replyMessage = "Hello from javascript"
To setup the message parameter, we just need to do:
invoke ws-javascript "put" using
z"message"
z"Hello World from COBOL!"
end-invoke
Then after the script has executed, we just need to use the 'get
' method..
invoke ws-javascript "get" using
z"replyMessage"
returning ws-message
end-invoke
if ws-message not equal null
invoke ws-pout "println" using ws-message
else
display "Javascript did not set a replyMessage var"
The completed COBOL example below uses a side file for the JavaScript too, the code is as follows:
$set ooctrl(+p) ooctrl(-f)
class-control.
cls-Script-EngineManager is
class "$JAVA$javax.script.ScriptEngineManager"
cls-Script-Engine is
class "$JAVA$javax.script.ScriptEngine"
cls-object is
class "$JAVA$java.lang.Object"
cls-System is
class "$JAVA$java.lang.System"
cls-PrintStream is
class "$JAVA$java.io.PrintStream"
cls-FileReader is
class "$JAVA$java.io.FileReader"
.
working-storage section.
01 ws-file object reference cls-FileReader.
01 ws-obj-sem object reference cls-Script-EngineManager.
01 ws-javascript object reference cls-Script-Engine.
01 ws-obj object reference cls-object.
01 ws-message object reference cls-object.
01 ws-pout object reference cls-PrintStream.
procedure division.
invoke cls-System "getout" returning ws-pout
invoke cls-FileReader "new" using
z"helloworld.js"
returning ws-file
end-invoke
invoke cls-Script-EngineManager "new"
returning ws-obj-sem
end-invoke
invoke ws-obj-sem "getEngineByName" using
"JavaScript" returning ws-javascript
end-invoke
invoke ws-javascript "put" using
z"message"
z"Hello World from COBOL!"
end-invoke
invoke ws-javascript "eval" using
ws-file
returning ws-obj-sem
end-invoke
invoke ws-javascript "get" using
z"replyMessage"
returning ws-message
end-invoke
if ws-message not equal null
invoke ws-pout "println" using ws-message
else
display "Javascript did not set a replyMessage var"
end-if
$if NO-FINALIZE not defined
if ws-message not equal null
invoke ws-message "finalize" returning ws-message
end-if
if ws-pout not equal null
invoke ws-pout "finalize" returning ws-pout
end-if
invoke ws-obj-sem "finalize" returning ws-obj-sem
invoke ws-javascript "finalize" returning ws-javascript
$end
stop run.
C:\jscripting\HelloWorld3>cobol cbljscript.cbl int();
Micro Focus Net Express V5
Version 6.0.00059 Copyright (C) 1984-2009 Micro Focus (IP) Limited.
URN AXCGG/AA0/00000
* Checking complete with no errors - starting code generation
* Generating cbljscript
* Data: 888 Code: 2528 Literals: 1296
C:\jscripting\HelloWorld3>runm cbljscript
Micro Focus Net Express V6.0.00059
RUN TIME ENVIRONMENT Copyright (C) 1984-2009 Micro Focus (IP) Limited.
URN AXCGG/AA0/00000
Hello World from COBOL!
Hello from javascript
As you can see from the code above, setting up the parameter is pretty easy to do, but sometimes we just want to execute a function in the scripting language such as:
function testMessage(msg)
{
print("testMessage : " + msg);
}
The ScriptEngine
object that we have created to use the scripting engine may implement an optional interface called javax.script.Invocable
; if the scripting engine we
are using does provide this interface, then a method called invokeFunction(..)
can be used.
In order to reduce the size of the COBOL code, I have coded a simple utils
class in Java as a simple proxy layer, the code is pretty simple but does make it easier
for COBOL to use the invokeFunction()
method.
import javax.script.*;
public class utils {
public static Invocable getInvocable(ScriptEngine obj) {
return (Invocable)obj;
}
public static Object invokeFunction(ScriptEngine obj,
String function, Object p1)
throws ScriptException, NoSuchMethodException {
Invocable iObj = getInvocable(obj);
return iObj.invokeFunction(function, p1);
}
}
Then from the COBOL side, we can just use the invokeFunction
above.
For example:
invoke cls-utils "invokeFunction" using
ws-javascript
z"testMessage"
z"Hello to function testMessage from COBOL"
Which gives us the following output when executed:
C:\jscripting\InvokeFunction>runm cbljscript
Micro Focus Net Express V6.0.00059
RUN TIME ENVIRONMENT Copyright (C) 1984-2009 Micro Focus (IP) Limited.
URN AXCGG/AA0/00000
testMessage : Hello to function testMessage from COBOL
The completed example is as follows:
$set ooctrl(+p) ooctrl(-f)
class-control.
cls-Script-EngineManager is
class "$JAVA$javax.script.ScriptEngineManager"
cls-Script-Engine is
class "$JAVA$javax.script.ScriptEngine"
cls-object is
class "$JAVA$java.lang.Object"
cls-System is
class "$JAVA$java.lang.System"
cls-PrintStream is
class "$JAVA$java.io.PrintStream"
cls-FileReader is
class "$JAVA$java.io.FileReader"
cls-Utils is
class "$JAVA$utils"
.
working-storage section.
01 ws-file object reference cls-FileReader.
01 ws-obj-sem object reference cls-Script-EngineManager.
01 ws-javascript object reference cls-Script-Engine.
01 ws-message object reference cls-object.
01 ws-pout object reference cls-PrintStream.
procedure division.
invoke cls-System "getout" returning ws-pout
invoke cls-FileReader "new" using
z"helloworld.js"
returning ws-file
end-invoke
invoke cls-Script-EngineManager "new"
returning ws-obj-sem
end-invoke
invoke ws-obj-sem "getEngineByName" using
"JavaScript" returning ws-javascript
end-invoke
invoke ws-javascript "eval" using
ws-file
returning ws-obj-sem
end-invoke
invoke cls-utils "invokeFunction" using
ws-javascript
z"testMessage"
z"Hello to function testMessage from COBOL"
returning ws-message
end-invoke
$if NO-FINALIZE not defined
if ws-file not equal null
invoke ws-file "finalize" returning ws-file
end-if
if ws-message not equal null
invoke ws-message "finalize" returning ws-message
end-if
if ws-pout not equal null
invoke ws-pout "finalize" returning ws-pout
end-if
if ws-obj-sem not equal null
invoke ws-obj-sem "finalize" returning ws-obj-sem
end-if
if ws-javascript not equal null
invoke ws-javascript "finalize" returning ws-javascript
end-if
$end
stop run.
Conclusions
Using a Java based scripting language from COBOL is quite easy, so feel free to use it. Now which scripting language should I use...?