Hello everyone,
I'm Norbert, a software developer and security consultant based in London. For the past six months I've been building a desktop COBOL transpiler called Easy COBOL Migrator and I'd like to share it with this community for feedback.
The tool converts COBOL source code to six modern languages: C++ 17, Java 17, C# 12, Python 3, Rust and Go. It uses a full compiler pipeline, not AI or pattern matching.
Building a parser for COBOL was one of the most challenging projects I've taken on. The language has depth that I didn't fully appreciate before starting. PIC clause parsing alone took weeks. Handling the interaction between level numbers, REDEFINES, OCCURS DEPENDING ON and group item nesting required multiple rewrites before I got the data structure generation right. PERFORM THRU with paragraph fall-through semantics was another area where I had to rethink my approach several times.
The pipeline has five stages:
- COPY Preprocessor - Resolves COPY and REPLACE directives with nested copybook support up to 10 levels and circular-include detection
- Lexer - Tokenizes source with auto-detection of fixed-format and free-format, handles 220+ keywords, column 7 indicators and continuation lines
- Parser - Recursive descent parser covering 36 statement types, builds a complete AST with expression trees, conditions and hierarchical data structures
- Semantic Analyzer - Symbol table construction, ambiguous name detection, OF/IN qualification resolution, paragraph/section existence checks, level 88 validation and type checking
- Code Generators - Six separate generators, each producing idiomatic output for the target language
Here is a small example. This COBOL:
IDENTIFICATION DIVISION.
PROGRAM-ID. PAYROLL-CALC.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-EMPLOYEE.
05 WS-NAME PIC X(25).
05 WS-HOURS PIC 9(3)V99.
05 WS-RATE PIC 9(3)V99.
05 WS-GROSS-PAY PIC 9(5)V99.
05 WS-TAX PIC 9(5)V99.
05 WS-NET-PAY PIC 9(5)V99.
01 WS-TAX-RATE PIC V99 VALUE 0.20.
01 WS-OVERTIME-MULT PIC 9V9 VALUE 1.5.
01 WS-OVERTIME-THRESHOLD PIC 9(3) VALUE 40.
01 WS-OVERTIME-HOURS PIC 9(3)V99 VALUE 0.
01 WS-REGULAR-HOURS PIC 9(3)V99 VALUE 0.
01 WS-COUNTER PIC 9(2) VALUE 0.
01 WS-PAY-GRADE PIC 9 VALUE 0.
88 JUNIOR VALUE 1.
88 SENIOR VALUE 2.
88 MANAGER VALUE 3.
PROCEDURE DIVISION.
MAIN-PROGRAM.
MOVE "Alice Johnson" TO WS-NAME
MOVE 45.00 TO WS-HOURS
MOVE 32.50 TO WS-RATE
MOVE 2 TO WS-PAY-GRADE
PERFORM CALCULATE-PAY
PERFORM DISPLAY-PAYSLIP
PERFORM DISPLAY-GRADE
STOP RUN.
CALCULATE-PAY.
IF WS-HOURS > WS-OVERTIME-THRESHOLD
COMPUTE WS-REGULAR-HOURS =
WS-OVERTIME-THRESHOLD
SUBTRACT WS-OVERTIME-THRESHOLD FROM WS-HOURS
GIVING WS-OVERTIME-HOURS
COMPUTE WS-GROSS-PAY =
(WS-REGULAR-HOURS * WS-RATE) +
(WS-OVERTIME-HOURS * WS-RATE *
WS-OVERTIME-MULT)
ELSE
COMPUTE WS-GROSS-PAY =
WS-HOURS * WS-RATE
END-IF
MULTIPLY WS-GROSS-PAY BY WS-TAX-RATE
GIVING WS-TAX ROUNDED
SUBTRACT WS-TAX FROM WS-GROSS-PAY
GIVING WS-NET-PAY.
DISPLAY-PAYSLIP.
DISPLAY "================================"
DISPLAY " PAYROLL SUMMARY"
DISPLAY "================================"
DISPLAY "Employee: " WS-NAME
DISPLAY "Hours: " WS-HOURS
DISPLAY "Rate: " WS-RATE
DISPLAY "Gross Pay: " WS-GROSS-PAY
DISPLAY "Tax (20%): " WS-TAX
DISPLAY "Net Pay: " WS-NET-PAY
DISPLAY "================================".
DISPLAY-GRADE.
EVALUATE TRUE
WHEN JUNIOR
DISPLAY "Grade: Junior"
WHEN SENIOR
DISPLAY "Grade: Senior"
WHEN MANAGER
DISPLAY "Grade: Manager"
WHEN OTHER
DISPLAY "Grade: Unknown"
END-EVALUATE.
Produces this C++:
// Transpiled from COBOL program: PAYROLL-CALC
// Generated by Easy COBOL Migrator on 2026-03-16 07:09:47
#include <iostream>
#include <string>
#include <cmath>
#include <cstdlib>
#include <cstdint>
// COBOL string move helper: truncate or right-pad to field width
inline std::string cobolMove(const std::string& src, int len) {
std::string r = src; r.resize(len, ' '); return r;
}
// COBOL string comparison: pad shorter operand with spaces
inline int cobolCmp(const std::string& a, const std::string& b) {
size_t len = std::max(a.size(), b.size());
std::string la = a, lb = b;
la.resize(len, ' '); lb.resize(len, ' ');
return la.compare(lb);
}
void main_program();
void calculate_pay();
void display_payslip();
void display_grade();
// WORKING-STORAGE variables
struct {
std::string ws_name = std::string(25, ' ');
double ws_hours = 0.0;
double ws_rate = 0.0;
double ws_gross_pay = 0.0;
double ws_tax = 0.0;
double ws_net_pay = 0.0;
} ws_employee;
double ws_tax_rate = 0.20;
double ws_overtime_mult = 1.5;
int ws_overtime_threshold = 40;
double ws_overtime_hours = 0;
double ws_regular_hours = 0;
int ws_counter = 0;
int ws_pay_grade = 0;
inline bool junior() { return ws_pay_grade == 1; }
inline bool senior() { return ws_pay_grade == 2; }
inline bool manager() { return ws_pay_grade == 3; }
int64_t return_code = 0;
void main_program() {
ws_employee.ws_name = cobolMove("Alice Johnson", 25);
ws_employee.ws_hours = 45.00;
ws_employee.ws_rate = 32.50;
ws_pay_grade = 2;
calculate_pay();
display_payslip();
display_grade();
exit(0);
}
void calculate_pay() {
if (ws_employee.ws_hours > ws_overtime_threshold) {
ws_regular_hours = ws_overtime_threshold;
ws_overtime_hours = ws_employee.ws_hours - (ws_overtime_threshold);
ws_employee.ws_gross_pay = ((ws_regular_hours * ws_employee.ws_rate) + ((ws_overtime_hours * ws_employee.ws_rate) * ws_overtime_mult));
} else {
ws_employee.ws_gross_pay = (ws_employee.ws_hours * ws_employee.ws_rate);
}
ws_employee.ws_tax = std::round((ws_employee.ws_gross_pay * ws_tax_rate) * 100.0) / 100.0;
ws_employee.ws_net_pay = ws_employee.ws_gross_pay - (ws_employee.ws_tax);
}
void display_payslip() {
std::cout << "================================" << std::endl;
std::cout << " PAYROLL SUMMARY" << std::endl;
std::cout << "================================" << std::endl;
std::cout << "Employee: " << ws_employee.ws_name << std::endl;
std::cout << "Hours: " << ws_employee.ws_hours << std::endl;
std::cout << "Rate: " << ws_employee.ws_rate << std::endl;
std::cout << "Gross Pay: " << ws_employee.ws_gross_pay << std::endl;
std::cout << "Tax (20%): " << ws_employee.ws_tax << std::endl;
std::cout << "Net Pay: " << ws_employee.ws_net_pay << std::endl;
std::cout << "================================" << std::endl;
}
void display_grade() {
if (junior()) {
std::cout << "Grade: Junior" << std::endl;
} else if (senior()) {
std::cout << "Grade: Senior" << std::endl;
} else if (manager()) {
std::cout << "Grade: Manager" << std::endl;
} else {
std::cout << "Grade: Unknown" << std::endl;
}
}
int main() {
main_program();
return 0;
}
The group item becomes an anonymous struct with typed fields. PIC 9(3)V99 maps to double with std::round for ROUNDED arithmetic. The level 88 conditions JUNIOR, SENIOR and MANAGER resolve to inline bool functions checking their parent variable WS-PAY-GRADE.
Coverage includes:
- All four divisions with full DATA DIVISION support (levels 01-88, all PIC/USAGE variants including COMP-3, OCCURS, REDEFINES, RENAMES, FILLER)
- LINKAGE SECTION transpilation with CALL BY REFERENCE, BY CONTENT and BY VALUE
- File I/O with OPEN/CLOSE/READ/WRITE/REWRITE/DELETE/START, record packing/unpacking and seek-based in-place update
- SORT/MERGE with key-field extraction supporting multi-key comparators with ascending/descending order
- COPY/REPLACE preprocessing with pseudo-text substitution and nested copybooks
- STRING/UNSTRING/INSPECT with full clause support
- SEARCH and SEARCH ALL for table operations
- 40+ intrinsic functions mapped to native equivalents in all 6 languages
- EXEC SQL, EXEC CICS and EXEC DLI blocks preserved as comments with migration notes
- IBM Enterprise COBOL, Micro Focus, GnuCOBOL, COBOL-85/2002/2014 dialect support
- Fixed-format and free-format with auto-detection
Known limitations: Report Writer and Screen Section are not supported. Indexed/relative file random access converts to sequential with a migration note recommending database replacement.
The tool is backed by 498 automated tests across all six target languages and has been validated against the NIST CCVS85 standard test suite.
There is a free demo available that converts single files up to 500 lines to C++ output: https://mecanik.dev/en/products/easy-cobol-migrator/
I would genuinely appreciate feedback from this community, especially:
- Are there Enterprise COBOL patterns or extensions that you think would be challenging for the parser? I want to know what real production code looks like beyond test suites.
- How heavily do your codebases rely on EXEC SQL and EXEC CICS? Currently the tool preserves these as comments with migration notes. Would generated scaffolding code (JDBC stubs for Java, ADO.NET stubs for C#) be more useful?
- Which target language is most relevant for your migration projects? Most of the industry seems to target Java but I'm curious what this community's experience has been.
I'm happy to test any COBOL samples you'd like to share (non-confidential of course) and report back on how the tool handles them.
Thank you for your time.
------------------------------
Norbert Elemer Boros
Software Developer & Security Consultant
Mecanik Dev Ltd
London
------------------------------