gnuCOBOL & LLM (and some python along the way)

Hi,

I hope this finds you all well. I would like to share the progress of an integration I have been working on for quite some time, and that is finally starting to take shape.

The following video demonstrates how to gain a new perspective on COBOL-generated reports, no matter how old they are. The prototype is crude, to say the least, but after fixing many issues, I’m now making rapid progress.

In essence, it shows how to use an LLM (LLaMa 3) to interactively extract data from reports and offer fresh perspectives on the available information. You might be surprised at how many new dimensions you can uncover from your existing reports.

I plan to remove Python in the final version of the solution so that it becomes a pure gnuCOBOL and C integration.

I will post updates on a weekly basis. This may eventually evolve into some form of community solution for local use, while a professional version could open the door to use LLMs in the cloud.

With best regards,
Emerson

Hi Emerson,

This is fascinating work — thanks for sharing! The idea of breathing new life into COBOL-generated reports using an LLM is both practical and exciting. Many organizations still rely on COBOL for critical workloads, and your approach could give them a way to extract new business value from reports that were traditionally considered “static.”

A couple of thoughts/questions that come to mind:

  • LLM choice: You mentioned LLaMa 3 — are you running it locally, or do you see the professional/cloud version leveraging APIs like OpenAI/Anthropic for enterprise use cases?

  • Report parsing: Are you aiming for a generic parsing layer that can handle any COBOL report format, or will it require some per-report tailoring (e.g., layout definitions, field mapping)?

  • COBOL-only integration: Removing Python and going for a pure gnuCOBOL/C integration is ambitious. Do you see this as a performance choice, or more for simplicity/portability?

  • Community solution: I like the idea of a local-first, open community tool. Many organizations won’t/can’t send data outside their walls but would jump at an offline solution.

Looking forward to your weekly updates — this has the potential to be a real bridge between legacy systems and modern AI-driven analysis.

Hi,

Glad you got it!

Regarding your questions:

LLM choice: The prototype is using Llama 3 locally because I wanted to test this particular model (I’m more used to working with Anthropic Claude, GPT, etc.). I plan to release a Community version that depends on a local LLM, and a Professional version that will let you choose between local LLMs (cheaper) and cloud-based LLMs (dozens of models via Amazon Bedrock).

Report parsing: Both Community and Pro versions will let you tailor the prompt (likely with some LangChain integration in Pro version), so you can use a generic prompt or a custom template adapted to each report. Many reports have custom formats, so there’s no chance a single prompt would fit all needs. Since we’re talking about LLMs, there will be little to no need to deal with layout definitions or field mapping. Maybe some—but so far I haven’t seen a real need for them.

COBOL-only integration: This will happen over time. The reason is that Python packaging can be distracting, and a pure COBOL approach seems more stable in the long run (GCC should provide all the tools required to make such a replacement possible). I can’t imagine a report created 20 years ago evolving to the point that it would require a new model—or at least a new way to interact with it.

Community solution: Even the Pro version will allow a local model, instead of relying solely on the cloud’s ability to provide models. And yes, you’re right: the walled-garden nature of mainframe/legacy data centers generally avoids sending data outside their perimeter (i.e., their own network).

I am also planning using gnuCOBOL CGI integrating with Streamlit to provide useful analysis, including charts on demand. Wouldn’t it be awesome? :wink:

With best regards, Emerson

Hello,

a quick update. Community version is taking shape. Here it is its roadmap (v.1b):

Community 0.1b — Lean Roadmap

1) Conversation history (short context)

  • Scope: keep N last exchanges in memory per session.

  • DoD: flag history.enabled, history.max_turns; test validates truncation.

  • Dep.: storage in file/SQLite.

  • Risk: context leakage — log final size.

2) Multi-model support

  • Scope: driver/adapter with unified interface (generate(), stream()), mapping Llama/Qwen/Mistral/…

  • DoD: switch by config (model.name, model.path, backend), smoke test per backend.

  • Dep.: inference libs (e.g., llama.cpp/gguf, vllm/ctransformers).

3) Report size limit per model

  • Scope: table model_limits.json (context/input, max output tokens).

  • DoD: validation before sending prompt; clear error message and chunking suggestion.

  • Dep.: token count normalizer (≈4 chars/token, override per backend).

4) Gnuplot support (dumb terminal)

  • Scope: run .gp scripts and capture ASCII output with set term dumb.

  • DoD: command gnuplot.run(script, data_path) returns ASCII block; tested with simple chart.

  • Dep.: gnuplot binary available; exec sandbox.

5) Prompt template

  • Scope: templates with placeholders ({{report}}, {{instructions}}, {{neg}}).

  • DoD: simple engine (Mustache/format), validation for missing placeholders.

  • Dep.: file templates/*.tmpl.

6) Negative prompt

  • Scope: optional field negative_prompt injecting avoidance instructions in template.

  • DoD: if present, appears in “Do not…” section of the template; basic A/B test.

7) Save conversation (last only)

  • Scope: persist only the last full session (input/output) for reopening.

  • DoD: file last_session.json with checksum; “restore last” command.

  • Dep.: same storage as item 1.

8) Mistral support

  • Scope: load Mistral weights (e.g., 7B/NeMo quantized) via same model adapter.

  • DoD: selection model.family = "mistral" + basic generation works.

  • Dep.: GGUF/compatible formats.

A few observations:

  • I’ve decided to use SQLite as the primary data store. It’s convenient and ubiquitous (macOS, Linux, Windows, and mainframes), and the same data file is compatible across all environments, including big-endian mainframes.
  • The report buffer supports up to 50 pages at 160 columns each, which should cover ~99% of analytics reports. The page limit may vary by model and processing cost (execution time, in this case); the Pro version will also account for cloud pricing.
  • The Pro version will include guardrails to prevent excessive cloud charges.

I stumbled across Gnuplot recently, and I’m impressed by its ability to generate ASCII charts instead of images. This allows older or SSH-only terminals to display basic analytics. The example below is a quick proof of concept and still needs improvements—for instance, each KPI should use its own distinct characters for visual differentiation. I don’t expect end users to rely on this; it’s mainly intended as a development aid. The model can also be directed to generate CSV data from the analysis, which can then be uploaded by some agent to somewhere (a path, a database, or even a cloud bucket) and consumed by more sophisticated tools such as Power BI, Tableau, or QuickSight.

Also, gnuplot can generate rich graphical charts as a image file (e.g., PNG, SVG, or PDF) with full control over colors, fonts, and annotations. This will be probably the way to interact with data in Pro version using CGI.

LLama 3.1 created the script that generated the chart below, but failed with some more complexes charts. Probably it would be better handled by an agent.

                Last Year vs This Year Sales Comparison                        
     60000 +-----------------------------------------------+                   
           |       + +++  + + +   + +   + + +  +   + +     | This YTD +-----+  
           |          |                                    | Last YTD +-----+  
     50000 |-+........|..................................+-|                   
           |          |                                    |                   
           |          |                                    |                   
     40000 |-+........|..................................+-|                   
           |          |                                    |                   
           |          |                                    |                   
     30000 |-+........|..................................+-|                   
           |          |                                    |                   
           |        ++|       +                            |                   
     20000 |-+......|||.......|..........................+-|                   
           |        |||       |                            |                   
           |        |||     + |    ++    ++++        +     |                   
     10000 |-+......|||.....|.|....||....||||........|...+-|                   
           |        |||     | |    ||    ||||        |     |                   
           |       +|||+  + | |   +||   +||||  +   + |     |                   
         0 +-----------------------------------------------+     

Just to make sure gnupplot is not underestimated, here it is a graphical example (there are hundreds of examples in the Internet).

gnuplot script
#!/usr/local/bin/gnuplot -persist
# set terminal pngcairo  transparent enhanced font "arial,10" fontscale 1.0 size 600, 400 
# set output 'boxes3d.6.png'
set boxwidth 0.4 absolute
set boxdepth 0.3
set style fill   solid 1.00 border
set grid nopolar
set grid xtics nomxtics ytics nomytics ztics nomztics nortics nomrtics \
 nox2tics nomx2tics noy2tics nomy2tics nocbtics nomcbtics
set grid vertical layerdefault   lt 0 linecolor 0 linewidth 1.000,  lt 0 linecolor 0 linewidth 1.000
unset key
set wall z0  fc  rgb "slategrey"  fillstyle  transparent solid 0.50 border lt -1
unset parametric
set view 59, 24, 1, 1
set style data lines
set xyplane at 0
set title "Full treatment: 3D boxes with pm3d depth sorting and lighting" 
set xrange [ * : * ] noreverse writeback
set x2range [ * : * ] noreverse writeback
set yrange [ 0.00000 : 6.00000 ] noreverse nowriteback
set y2range [ * : * ] noreverse writeback
set zrange [ * : * ] noreverse writeback
set cbrange [ * : * ] noreverse writeback
set rrange [ * : * ] noreverse writeback
set pm3d depthorder base
set pm3d interpolate 1,1 flush begin noftriangles border linewidth 1.000 dashtype solid corners2color mean
set pm3d lighting primary 0.5 specular 0.2 spec2 0
set colorbox vertical origin screen 0.9, 0.2 size screen 0.05, 0.6 front  noinvert bdefault
rgbfudge(x) = x*51*32768 + (11-x)*51*128 + int(abs(5.5-x)*510/9.)
ti(col) = sprintf("%d",col)
NO_ANIMATION = 1
## Last datafile plotted: "candlesticks.dat"
splot for [col=1:5] 'candlesticks.dat' using 1:(col):(col*column(col)):(rgbfudge($1))       with boxes fc rgb variable

Hi,

I need reports to test the solution. I’m looking for samples of raw print data that use the following control characters standards:

1) Mainframe line-printer carriage control (1st column/byte)

  • ASA carriage control (AKA FORTRAN carriage control)
    First column is a printable char that controls vertical motion:
    ' ' = single-space, '0' = double-space, '-' = triple-space, '1' = new page, '+' = overprint; digits 2–9 = skip to channel n.
    (On z/OS datasets, look for RECFM=FBA/VBA — “A” = ASA.)
  • Machine/Channel carriage control
    First byte is a non-printing control value that maps to carriage-tape channels (1–12).
    (On z/OS, RECFM=FBM/VBM — “M” = machine control.)

2) Plain text with C0/C1 control characters (ASCII/EBCDIC)

  • ASCII C0: LF (0x0A), CR (0x0D), FF (0x0C page break), VT (0x0B), HT (0x09).
    Classic “.txt” reports often use form feed to paginate.
  • EBCDIC controls: equivalents for NL/CR/FF, common in mainframe spooled text.

3) Terminal/printer escape-sequence languages (in-band ESC codes)

  • ANSI X3.64 / ECMA-48 (VT100 “ANSI escapes”)
    CSI sequences like ESC[2J, ESC[1m (bold), ESC[H (cursor); used mostly on screens but sometimes captured/printed.
  • HP PCL (PCL3/4/5)
    ESC-prefixed codes for font, bold, margins, page breaks; ubiquitous on lasers.
    (PCL6/XL is structured binary—less “texty,” but same role.)
  • Epson ESC/P, ESC/P2
    Dot-matrix era: ESC E (bold), ESC @ (init), etc.; widely used for invoices/reports.
  • IBM SCS (SNA Character Stream)
    EBCDIC stream with control orders for line/spacing/forms; used by 3270/Host printers (e.g., 3287/3812).
  • Xerox LCDS/Metacode + DJDE
    Text/Metacode streams with in-line DJDE controls for forms/resources on large printers.

4) Page description / print data streams (not just “control chars,” but replaced many text reports)

Please, post here just the first 2 pages of the report. No need of extensive reports.

With best regards, Emerson