Node.js - Group home

Language interoperability with IBM® Open Enterprise SDK for Node.js: Part 2 - PL/I and COBOL

  

IBM® Open Enterprise SDK for Node.js is a JavaScript runtime that provides a secure, module-driven, highly scalable approach to accelerate digital transformation on z/OS.

This series of articles will demonstrate how application developers can leverage scripts and modules written in common z/OS languages such as Restructured Extended Executor (REXX™), PL/I, and COBOL in their IBM® Open Enterprise SDK for Node.js application with the goal of modernizing their application architecture.

Part 1 of this series of focuses on REXX interoperability with IBM Open Enterprise SDK for Node.js.  Part 2 of this series focuses on interoperability with the statically compiled languages PL/I and COBOL.

Getting Started

Download IBM® Open Enterprise SDK for Node.js and follow the IBM Documentation installation instructions to install IBM® Open Enterprise SDK for Node.js.

Leverage PL/I Modules in Node.js

Programming Language 1 (PL/I), is a full-function, general-purpose, high-level programming language for z/OS.  It first appeared in 1964 and is still heavily used in z/OS applications today.

PL/I is typically suited for the development of:
  • Commercial applications
  • Engineering/scientific applications
  • and more
With the advent of modern cross-platform interpreted languages such as Python and JavaScript, the skill availability of such languages has far exceeded that of PL/I. Modern languages can help extend existing PL/I modules by assisting with the processing and presentation of PL/I results. Therefore, it is important to enable facilities that allow interoperability with PL/I such that existing applications can be extended with modern languages such as JavaScript.

This section will demonstrate how to invoke PL/I modules, residing in either PDS or HFS file systems, from IBM Open Enterprise SDK for Node.js.

Step 1: Prepare your files

Follow one of the three steps below to prepare your PL/I module:
  • Create a new PL/I  module residing in a HFS file
  • Copy an existing PL/I module that resides in a PDS (Partitioned Dataset) to an HFS file
  • Reference an existing PL/I module that resides in a PDS via an external link

Create a new PL/I module residing in a HFS file
Create a Hello World PL/I source file by entering the following text into a file named my.pli:
*process display(std);

plimain: proc options(main);
declare s character (100) varying;

  display("Hello World!");

put skip list ('Your input:');
get list (s);
put skip list (s);

 end;
This program will also read input from stdin using the get PL/I function.

Compile and link the source file into an executable module as follows:
pli -qlp=64 -qlimits=extname=31 my.pli -o my.prog

Run the pli program to verify:
echo "MY STDIN" | ./my.prog

The following output should be displayed:
Hello World!
Your input:
MY STDIN


Copy an existing PL/I module that resides in a PDS  (Partitioned Dataset) to an HFS file
To copy an existing PL/I module residing in a PDS to an HFS file, run the following cp command:
cp -X "//'HLQ.PDS(PLI)'" my.prog

This command will copy the PL/I module to an HFS file named
my.prog in your current working directory.  The -X option will preserve the execution bit.


Reference an existing PL/I module that resides in a PDS
Create an HFS external link to the PDS PL/I module to avoid the need to copy the PDS member: 
ln -e  "//'HLQ.PDS(PLI)'" my.prog

If you are using this method, you will need to set the STEPLIB environment variable to include the PDS dataset prior to running your PL/I module so that z/OS can find the PL/I module:
export STEPLIB=HLQ.PDS:$STEPLIB


Step
2: Create a Node.js application to invoke the PL/I module


U
se the Child process module included within Node.js to write a Node.js application that executes the PL/I module, my.prog, created above.  The spawn method in the Child process module will be used to spawn the PL/I application, ./my.prog.

Create a file named index.js with the following text:
const { spawn } = require('child_process');
const pli = spawn('./my.prog');

pli.stdout.on('data', (data) => {
  console.log(`PL/I stdout: ${data}`);
});

pli.stderr.on('data', (data) => {
console.error(`PL/I stderr: ${data}`);
});

pli.on('close', (code) => {
console.log(`PL/I process exited with code ${code}`);
});

pli.stdin.write("My STDIN\n");

The above example registers 3 callback events, one to report the stdout output, another one to report the stderr output, and a 'close' callback that is triggered when the PL/I process terminates.   It also writes "MY STDIN\n" to stdin, which the PL/I program will read and display.

To run the Node.js application, enter the following command:
node index.js

The following output will be displayed:

PL/I stdout: HELLO WORLD

My STDIN

PL/I process exited with code 0


Create a Native Node.js Module that calls PL/I functions


IBM Open Enterprise SDK for Node.js supports native Node.js modules which are add-on libraries written
in C/C++.  Users can leverage this capability to perform interlanguage calls to PL/I via Node.js by providing a C/C++ binding interface to PL/I.  This is made possible because the IBM Enterprise PL/I for z/OS compiler supports the generation of C/C++ compatible 64-bit XPLINK LE-based objects.

The following steps will demonstrate how to expose a PL/I function as an API in Node.js.


Step 1: Start a New Project


Start a terminal session and connect to your z/OS UNIX System Services (z/OS UNIX) environment.  
Create a directory named mypli-native-module and change the current working directory to it.  
mkdir my-pli-native-module
cd my-pli-native-module


Use npm to create a Node.js package as follows:

npm init --yes

This command will create a package.json file which describes your package and its dependencies. The file will contain the following text:
{
"name": "my-pli-native-module",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
By default, the main source file for the project is index.js.



Step 2:
Set up the Native Node.js Module

The next step is to install the node-addon-api (C++ native add-on interface) and the bindings module as dependencies to the project as follows:
npm install node-addon-api bindings

The next step is to specify the input source files for the native Node.js module by creating a binding.gyp file with the following text:
{
"targets": [
   {
     "target_name": "plibinding",
     "sources": [ "plibinding.cc" ],
     "include_dirs": [
       "<!@(node -p \"require('node-addon-api').include\")"
     ],
     'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
   }
]
}
The binding.gyp file specifies the input source files (plibinding.cc), the target native module name (plibinding), and the location of the Node.js API include headers.
Create the plibinding.cc source file with the following text:
#include <ctype.h>
#include <napi.h>

extern "C" int __getPLIData();

Napi::Number getPLIData(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
  _ae_thread_swapmode(__AE_EBCDIC_MODE); // Switch to EBCDIC mode before entering PL/I
int data = __getPLIData();
 _ae_thread_swapmode(__AE_ASCII_MODE);  // Switch back to ASCII mode after exiting PL/I
return Napi::Number::New(env, data);
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
Napi::String name = Napi::String::New(env, "getPLIData");
Napi::Function function = Napi::Function::New(env, getPLIData);
exports.Set(name, function);
return exports;
}

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
In plibinding.cc above, the function getPLIData is defined as a Node.js interface and is the sole API exposed in the module.   It simply returns a value from the PL/I function __getPLIData().  Before the call to the PL/I function __getPLIData(), the LE character mode is set to EBCDIC, and is then switched back to ASCII upon exit.  This is because Node.js for z/OS uses the ASCII LE character mode, while PL/I module uses the EBCDIC LE character mode.  Note that if the PL/I source code has the potential to throw an exception, you should prepare a signal handler to catch such exceptions.  Since the code is switching to EBCDIC mode prior to entering PL/I, the character mode can be used to determine if the exception came from PL/I or Node.js at the time the signal is caught.

Next, create the PL/I source code that defines the function __getPLIData(), which returns the value 10, by entering the following text to a PL/I source file named num.pli:

*process display(std);

__getPLIData: proc ext("__getPLIData")
  returns(fixed bin(63) byvalue);
   return(10);
 end;

As of IBM® Open Enterprise SDK for Node.js 16.0, node-gyp supports the compilation of PL/I source files.   The following step will work with IBM® Open Enterprise SDK for Node.js 16.0 and later.  For IBM SDK for Node.js - z/OS, 14.0 or earlier, skip to the section: Building PL/I source files for IBM SDK for Node.js - z/OS, 14.0 or earlier.

Simply add the PL/I source file, num.pli to the "sources" section in the binding.gyp (highlighted in bold) as follows:
{
"targets": [
   {
     "target_name": "plibinding",
     "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
     ],
    "sources": [ "plibinding.cc", "num.pli" ],
     "defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
   }
]
}

Building PL/I source files for IBM SDK for Node.js - z/OS, 14.0 or earlier

(Skip this section if you are building with IBM® Open Enterprise SDK for Node.js 16.0)

Compile num.pli into a 64-bit num.o object file with the following pli compiler command:
pli -qlp=64 -qlimits=extname=31 num.pli -c num.o

Next, add the libraries line to
binding.gyp (highlighted in bold), such that the num.o object file is linked with the addon:
{
"targets": [
   {
     "target_name": "plibinding",
     "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
     ],
     "sources": [ "plibinding.cc" ],
      "libraries": [ "../num.o" ],
     "defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
   }
]
}


Step
3: Build and test the Native Node.js Module


To build the add-on, run the following npm command:
npm install

This command will compile all source files and link them to generate the add-on DLL.

The add-on can be imported in JavaScript by inserting the following source code into index.js:
const plibinding = require('bindings')('plibinding');

To call getPLIData() and display its result from Java
Script, insert the following line:
console.log(plibinding.getPLIData());

To run the application
, run the following command:
node index.js

The following text, originating from the PL/I function __getPLIData(), should be emitted to STDOUT:
10


Leverage COBOL Programs in Node.js

Common Business-Oriented Language (COBOL) is a programming language similar to English that is widely used to develop business-oriented applications in the area of commercial data processing.

This section will demonstrate how to invoke COBOL programs, residing in either PDS or HFS file systems, from IBM Open Enterprise SDK for Node.js.

Step 1: Prepare your files

Follow one of the steps below to prepare your COBOL program:
  • Create and compile a COBOL program residing in HFS
  • Copy an existing COBOL program that resides in a PDS (Partitioned Dataset) to an HFS file
  • Reference an existing COBOL program that resides in a PDS


Create and compile a COBOL program residing in HFS


If a COBOL program is not available, then create a
Hello World COBOL source file by entering the following text to a file named my.cob:
       IDENTIFICATION DIVISION.
      PROGRAM-ID.
          COBPROG.
      ENVIRONMENT DIVISION.
      DATA DIVISION.
      PROCEDURE DIVISION.
          DISPLAY "HELLO WORLD".
           STOP RUN.
Compile and link the source file into an executable module by running the cob2 compiler as follows:
cob2 my.cob -o my.prog

Run the COBOL program to verify:
./my.prog

The following output should be displayed:
HELLO WORLD


Copy an existing COBOL program that resides in a PDS (Partitioned Dataset) to an HFS file

To copy an existing COBOL program residing in a PDS to an HFS file, run the following cp command:
cp -X "//'HLQ.PDS(COBOL)'" my.prog 

This command will copy the COBOL program to an HFS file named
my.prog in your current working directory.  The -X option will preserve the execution bit.

Reference an existing COBOL program that resides in a PDS

Create an HFS link to the PDS COBOL program to avoid the need to copy the PDS member: 
ln -e  "//'HLQ.PDS(COBOL)'" my.prog
 
If you are using this method, you will need to set the STEPLIB environment variable to include the PDS dataset prior to running your COBOL program so that z/OS can find the COBOL Program:
export STEPLIB=HLQ.PDS:$STEPLIB


Step
2: Create the Node.js program to invoke the COBOL module

Use the Child process module included in the Node.js runtime to write a Node.js application that executes the COBOL program, my.prog, created above.  The example below uses the spawn method in the Child process module to spawn the COBOL application, ./my.prog:

Create a file named index.js with the following text:
const { spawn } = require('child_process');
const cobol = spawn('./my.prog');

cobol.stdout.on('data', (data) => {
  console.log(`COBOL stdout: ${data}`);
});

cobol.stderr.on('data', (data) => {
console.error(`COBOL stderr: ${data}`);
});

cobol.on('close', (code) => {
console.log(`COBOL process exited with code ${code}`);
});

The above example registers 3 callback events, one to report the stdout output, another one to report the stderr output, and a 'close' callback that is triggered when the COBOL application terminates.  

To run the Node.js application, enter the following command:
node index.js

The following output will be displayed:
COBOL stdout: HELLO WORLD

COBOL process exited with code 0


More information


If you have any questions or concerns, please comment below.