Standalone COBOL processor following the markdown-processor.ts pattern: - No LanguageProvider modification — COBOL uses regex, not tree-sitter - No SupportedLanguages enum change — standalone processor pattern New files: - cobol-processor.ts — orchestrator (processCobol, isCobolFile, isJclFile) - cobol/cobol-preprocessor.ts — regex state machine extraction (~888 LOC) - cobol/cobol-copy-expander.ts — COPY statement expansion with circular detection - cobol/jcl-parser.ts — JCL job/step/DD extraction - cobol/jcl-processor.ts — JCL graph node creation Extraction produces: - Module nodes (PROGRAM-ID) - Function nodes (paragraphs) - Namespace nodes (sections) - Property nodes (data items) - CALLS edges (PERFORM intra-file, CALL cross-program) - IMPORTS edges (COPY statements) - CONTAINS edges (section → paragraph hierarchy) Pipeline integration: single processCobol() call in Phase 2.6 54 new tests (33 COBOL + 21 JCL), all 3889 tests pass.
9.4 KiB
COBOL Deep Indexing
Beyond basic symbol extraction (program name, paragraphs, CALL, PERFORM, COPY), GitNexus performs deep indexing of COBOL-specific constructs: data items, EXEC SQL/CICS blocks, file declarations, FD entries, ENTRY points, and MOVE statements.
Data Items
Level Numbers
| Level Range | Meaning | Graph Node Type |
|---|---|---|
| 01 | Record (group item) | Record |
| 02-49 | Elementary/group items | Property |
| 66 | RENAMES | Property |
| 77 | Independent item | Property |
| 88 | Condition name | Const |
FILLER items are skipped (no useful name for the graph).
Clauses Parsed
The parseDataItemClauses() function extracts these clauses from the trailing text of a data item declaration:
| Clause | Pattern | Example |
|---|---|---|
PIC / PICTURE |
\bPIC(?:TURE)?\s+(?:IS\s+)?(\S+) |
PIC X(30), PICTURE IS 9(5)V99 |
USAGE |
\bUSAGE\s+(?:IS\s+)?(COMP|BINARY|...) |
USAGE IS COMP-3, BINARY |
REDEFINES |
\bREDEFINES\s+([A-Z][A-Z0-9-]+) |
REDEFINES WK-DATE-NUM |
OCCURS |
\bOCCURS\s+(\d+) |
OCCURS 12 TIMES |
Standalone COMP variants (without the USAGE keyword) are also detected: COMP, COMP-1 through COMP-6, COMP-X, BINARY, PACKED-DECIMAL.
Data Hierarchy
Data items form a hierarchical structure based on level numbers. The extractor uses a stack algorithm:
Processing order:
01 WK-RECORD -> push {01, WK-RECORD} -> parent: Module
05 WK-NAME -> push {05, WK-NAME} -> parent: WK-RECORD (01 < 05)
10 WK-FIRST -> push {10, WK-FIRST} -> parent: WK-NAME (05 < 10)
10 WK-LAST -> pop WK-FIRST, push -> parent: WK-NAME (05 < 10)
05 WK-CODE -> pop WK-LAST, WK-NAME -> parent: WK-RECORD (01 < 05)
88 WK-ACTIVE -> (88 handled separately) -> parent: WK-CODE
The stack maintains items where each entry's level is strictly less than the next. When a new item arrives with a level <= the top of stack, items are popped until the stack top has a smaller level. A CONTAINS edge is created from the stack top to the new item.
For 88-level condition names, the parent is the immediately preceding non-88 data item (found by scanning backwards).
Annotated Example
01 WK-EMPLOYEE.
05 WK-EMP-ID PIC 9(6).
05 WK-EMP-NAME PIC X(30).
05 WK-EMP-STATUS PIC X(01).
88 WK-ACTIVE VALUE "A".
88 WK-INACTIVE VALUE "I".
05 WK-SALARY PIC 9(7)V99 COMP-3.
05 WK-DEPT PIC X(04) OCCURS 3 TIMES.
Produces:
Recordnode:WK-EMPLOYEE(level 01, section: working-storage)Propertynodes:WK-EMP-ID,WK-EMP-NAME,WK-EMP-STATUS,WK-SALARY,WK-DEPTConstnodes:WK-ACTIVE(values:A),WK-INACTIVE(values:I)CONTAINSedges:WK-EMPLOYEE -> WK-EMP-ID,WK-EMPLOYEE -> WK-EMP-NAME, etc.CONTAINSedges:WK-EMP-STATUS -> WK-ACTIVE,WK-EMP-STATUS -> WK-INACTIVE
Data Item Cap
A maximum of 500 data items per file (MAX_DATA_ITEMS_PER_FILE) are processed. Some COBOL programs (especially after COPY expansion) can have 10,000+ data items, which would cause graph bloat and push the V8 relationship Map past its 16.7M entry limit across thousands of files.
The cap applies after extraction: the first 500 items in source order are kept. Since 01-level records appear first, critical top-level structure is preserved.
EXEC SQL
EXEC SQL blocks are accumulated across lines between EXEC SQL and END-EXEC, then parsed as a unit.
Operation Classification
The first SQL keyword determines the operation:
| First Keyword | Operation |
|---|---|
SELECT |
SELECT |
INSERT |
INSERT |
UPDATE |
UPDATE |
DELETE |
DELETE |
DECLARE |
DECLARE |
OPEN |
OPEN |
CLOSE |
CLOSE |
FETCH |
FETCH |
| (anything else) | OTHER |
Table Extraction
Tables are extracted from SQL clauses:
| Clause Pattern | Example |
|---|---|
FROM <table> |
SELECT * FROM EMPLOYEES |
INTO <table> |
INSERT INTO EMPLOYEES |
UPDATE <table> |
UPDATE EMPLOYEES SET ... |
JOIN <table> |
LEFT JOIN DEPARTMENTS ON ... |
Cursor Detection
EXEC SQL
DECLARE C-EMPLOYEES CURSOR FOR
SELECT EMP-ID, EMP-NAME FROM EMPLOYEES
WHERE DEPT = :WK-DEPT
END-EXEC
Extracts: cursor C-EMPLOYEES, table EMPLOYEES, host variable WK-DEPT.
Host Variables
Host variables are COBOL variables referenced in SQL with a : prefix. The colon is stripped:
WHERE EMP-ID = :WK-EMP-ID AND DEPT = :WK-DEPT
Extracts: WK-EMP-ID, WK-DEPT.
Graph Output
CodeElementnode per table, with descriptionsql-table op:{OP}CodeElementnode per cursor, with descriptionsql-cursorACCESSESedge from Module to each CodeElement- Deduplication: if the same table appears in multiple SQL blocks, only one node is created
EXEC CICS
EXEC CICS blocks are accumulated and parsed similarly to SQL blocks.
Command Detection
Two-word commands are detected first (matched against the block start):
SEND MAP, RECEIVE MAP, SEND TEXT, SEND CONTROL, READ NEXT, READ PREV
If no two-word command matches, the first word is used (e.g., LINK, XCTL, RETURN, READ, WRITE).
Extraction
| Element | Pattern | Example |
|---|---|---|
| MAP name | MAP('name') or MAP("name") |
EXEC CICS SEND MAP('EMPMENU') |
| PROGRAM name | PROGRAM('name') or PROGRAM("name") |
EXEC CICS LINK PROGRAM('BGTABUP') |
| TRANSID | TRANSID('name') or TRANSID("name") |
EXEC CICS START TRANSID('EMP1') |
Graph Output
- MAP:
CodeElementnode with descriptioncics-map cmd:{CMD}+ACCESSESedge from Module - PROGRAM:
CALLSedge (cross-program call via CICS LINK/XCTL) - TRANSID:
CodeElementnode with descriptioncics-transid cmd:{CMD}+ACCESSESedge from Module
Annotated Example
EXEC CICS
SEND MAP('EMPMENU')
MAPSET('EMPSET')
FROM(WK-MAP-DATA)
ERASE
END-EXEC
Produces:
CodeElementnode:EMPMENU(description:cics-map cmd:SEND MAP)ACCESSESedge: Module ->EMPMENU
File Declarations
SELECT statements in the INPUT-OUTPUT SECTION are accumulated across multiple lines (until a period terminator) and parsed for:
| Clause | Pattern | Example |
|---|---|---|
| SELECT | SELECT <name> |
SELECT MASTER-FILE |
| ASSIGN | ASSIGN TO <file> |
ASSIGN TO "MASTER.DAT" |
| ORGANIZATION | ORGANIZATION IS <type> |
ORGANIZATION IS INDEXED |
| ACCESS | ACCESS MODE IS <mode> |
ACCESS MODE IS DYNAMIC |
| RECORD KEY | RECORD KEY IS <field> |
RECORD KEY IS WK-EMP-ID |
| FILE STATUS | FILE STATUS IS <field> |
FILE STATUS IS WK-FILE-STATUS |
Graph Output
CodeElementnode with description containing all parsed clauses (e.g.,select org:INDEXED access:DYNAMIC key:WK-EMP-ID status:WK-FILE-STATUS assign:MASTER.DAT)RECORD_KEY_OFedge: from Property node to CodeElement (confidence 0.8)FILE_STATUS_OFedge: from Property node to CodeElement (confidence 0.8)
FD Entries
FD (File Description) entries associate a file name with its record layout:
FD MASTER-FILE.
01 MASTER-RECORD.
05 MR-EMP-ID PIC 9(6).
05 MR-EMP-NAME PIC X(30).
The extractor tracks pendingFdName state: when an FD line is seen, the next 01-level data item becomes its record.
Graph Output
CodeElementnode with descriptionfd record:{recordName}CONTAINSedge: FD CodeElement -> Record nodeCONTAINSedge: SELECT CodeElement -> FD CodeElement (linking file declaration to file description)
ENTRY Points
The ENTRY statement defines additional entry points into a COBOL program (in addition to the main program entry):
ENTRY "SUBPROG" USING WK-PARAM-1 WK-PARAM-2.
Graph Output
Constructornode with descriptionentry params:{param1},{param2}(or justentryif no parameters)CONTAINSedge: Module -> Constructor- Symbol table entry (so the entry point is discoverable by name)
PROCEDURE DIVISION USING
PROCEDURE DIVISION USING WK-INPUT-REC WK-OUTPUT-REC.
The USING clause identifies parameters received by the program from its caller.
Graph Output
RECEIVESedge: Module -> Property (for each parameter name, confidence 0.8)
MOVE Statements
MOVE statements are extracted but currently only stored in the regex results (not emitted as graph edges):
MOVE WK-NAME TO OUT-NAME.
MOVE CORRESPONDING WK-INPUT TO WK-OUTPUT.
Extraction Details
- Source and target identifiers are captured
CORRESPONDINGkeyword is tracked (bulk field-by-field move)- Figurative constants (SPACES, ZEROS, LOW-VALUES, HIGH-VALUES, QUOTES, ALL) are skipped
- The enclosing paragraph (
caller) is tracked for context
DATA_FLOW edges from MOVE statements are reserved for a future release.
Source Files
gitnexus/src/core/ingestion/cobol-preprocessor.ts-- All extraction logic, clause parsers, EXEC block parsersgitnexus/src/core/ingestion/workers/parse-worker.ts--processCobolRegexOnly(), graph node/edge emissiongitnexus/src/core/ingestion/parsing-processor.ts-- Sequential fallback with sameMAX_DATA_ITEMS_PER_FILEcap