Host I/O: Bidirectional Subprocess Communication (2!:2)

This patch implements the 2!:2 foreign function, enabling J to spawn subprocesses and communicate with them bidirectionally through pipes.

Patch: host-io-2_2.patch


Overview

The 2!:2 foreign function spawns an external process and returns file handles that can be used to communicate with it:

handles =. 2!:2 'bc -l'    NB. Start interactive bc calculator
handles                     NB. Returns: pid stdin_handle stdout_handle

This enables J to:


What It Does

Implements 2!:2 as a working subprocess communication system on Unix platforms. The original J source had this function commented out with #if 0 and marked “doesn’t work”.

The implementation includes line buffering for real-time interactive behavior.

Usage

NB. Start a subprocess
handles =. 2!:2 'python3 -i'
'pid stdin stdout' =. handles

NB. Write to subprocess stdin
'print(2+2)\n' 1!:2 stdin

NB. Read from subprocess stdout
result =. 1!:1 stdout

NB. Wait for subprocess to finish
exit_status =. 2!:3 pid

How It Works

  1. Creates two pipes: One for stdin (J → subprocess), one for stdout (subprocess → J)
  2. Forks the process: Parent continues in J, child becomes the subprocess
  3. Redirects I/O: Child’s stdin/stdout are connected to the pipes
  4. Configures buffering: Line buffering for stdin, unbuffered for stdout
  5. Registers handles: Pipes are added to J’s file table for use with 1!:1, 1!:2, etc.

Technical Implementation

The implementation modifies jsrc/xh.c with a new jthostio function:

DF1(jthostio){
  // Create pipes for bidirectional communication
  pipe(fi);  // stdin pipe: J writes to fi[1], child reads fi[0]
  pipe(fo);  // stdout pipe: child writes to fo[1], J reads fo[0]

  switch(fork()) {
    case 0:  // Child process
      dup2(fi[0], STDIN_FILENO);   // Redirect stdin
      dup2(fo[1], STDOUT_FILENO);  // Redirect stdout
      execl("/bin/sh", "-c", command, NULL);

    default:  // Parent process
      fin = fdopen(fi[1], "w");   // Write to child's stdin
      fout = fdopen(fo[0], "r");  // Read from child's stdout

      // Line buffered output, unbuffered input for real-time behavior
      setvbuf(fin, NULL, _IOLBF, 0);
      setvbuf(fout, NULL, _IONBF, 0);

      // Add to J's file table...
  }
}

Buffering Configuration

Direction Mode Reason
J → Child Line buffered (_IOLBF) Commands are typically line-based; flush on newline ensures timely delivery
Child → J Unbuffered (_IONBF) J should see output immediately as the child produces it

Complete Example

NB. Interactive session with Python
py =. 2!:2 'python3 -u'    NB. -u for unbuffered Python output
'pid stdin stdout' =. py

NB. Define a function in Python
'def greet(name): return f"Hello, {name}!"\n' 1!:2 stdin

NB. Call it
'print(greet("J"))\n' 1!:2 stdin

NB. Read the response
response =. 1!:14 stdout   NB. Use 1!:14 for line-trimmed read
echo response              NB. "Hello, J!"

NB. Clean up
'exit()\n' 1!:2 stdin
status =. 2!:3 pid

Platform Support

Platform Status
Linux Supported
FreeBSD Supported
macOS Supported
Windows Not implemented (returns domain error)

Applying the Patch

cd /path/to/jsource
git apply host-io-2_2.patch