Step 1 — 行ラベル(シーケンス番号)の除去

列1〜6のシーケンス番号を単純に削除する。 Pythonのスライスで line[6:] とすれば列7以降だけが残る。 ただしタブ文字の混入対策として前処理でタブを展開しておく。

Python — Step 1: 行ラベル除去
def remove_sequence_numbers(lines: list[str]) -> list[str]:
    """列1〜6のシーケンス番号を除去し、列7以降を返す。"""
    result = []
    for line in lines:
        # 改行を除去
        line = line.rstrip('\n').rstrip('\r')
        # タブを空白8文字に展開(タブ混入対策)
        line = line.expandtabs(8)
        # 6文字以下はシーケンス番号だけの空行とみなしスキップ
        if len(line) <= 6:
            continue
        # 列7以降を取り出す(Python スライスは0始まりなのでインデックス6から)
        result.append(line[6:])
    return result
before / after
Before: "000100 PROCEDURE DIVISION.                                             "
After:  " PROCEDURE DIVISION.                                             "

Before: "000200     MOVE 'ABC' TO WS-VAR.                                        "
After:  "     MOVE 'ABC' TO WS-VAR.                                        "

💡 列7(標識)は残す

この時点では標識列(列7)はまだ保持したままにする。Step 2 でコメント判定に使うため、まだ除去しない。

Step 2 — コメント行の削除

Step 1 後の行では列7がインデックス0になっている(元の列7はスライスで先頭に来る)。 先頭文字が * または / の場合はコメント行として行全体を削除する。

COBOLにはインラインコメント(行の途中から始まるコメント)は原則存在しない。 コメントは必ず標識列で示されるため、この判定は非常にシンプルだ。

Python — Step 2: コメント行削除
def remove_comments(lines: list[str]) -> list[str]:
    """
    標識列(Step1後の先頭文字)が * または / の行を削除する。
    Step1(remove_sequence_numbers)適用後のリストに対して呼ぶこと。
    """
    result = []
    for line in lines:
        if not line:
            continue
        indicator = line[0]
        if indicator in ('*', '/'):
            # コメント行 — スキップ
            continue
        result.append(line)
    return result
before / after
Before(Step1後):
 IDENTIFICATION DIVISION.
* このプログラムはサンプルです
/ ページ区切り
 PROGRAM-ID. SAMPLE.

After(コメント削除後):
 IDENTIFICATION DIVISION.
 PROGRAM-ID. SAMPLE.

⚠️ デバッグ行(標識 D)の扱い

標識列が D のデバッグ行は WITH DEBUGGING MODE が ENVIRONMENT DIVISION で宣言されている場合のみ有効になる。 宣言がない場合はコメント扱いになるため、本実装ではデフォルトで削除する。 必要であれば DEBUGGING_MODE = False のようなフラグで制御できるようにしておくとよい。

Step 3 — 識別領域(73〜80列)の除去

Step 1 で列1〜6を除去したので、元の列73〜80 は今の行では位置66〜73(0始まりでインデックス66〜73)にある。 元の列7〜72 が今の先頭から66文字分なので、line[:66] で切り落とせばよい。

ただし継続行結合の前に識別領域を除去しておく必要があるため、このステップは継続行処理(PART 04)より先に行う。

Python — Step 3: 識別領域除去
def remove_identification_area(lines: list[str]) -> list[str]:
    """
    識別領域(元の列73〜80)を除去する。
    Step1(remove_sequence_numbers)適用後のリストに対して呼ぶこと。
    元の列7〜72 = Step1後の先頭から66文字(インデックス0〜65)。
    行末の空白もついでにstrip()で除去する。
    """
    result = []
    for line in lines:
        # 66文字に切り落とし、末尾空白を除去
        trimmed = line[:66].rstrip()
        result.append(trimmed)
    return result
before / after
Before:  "     MOVE 'ABC' TO WS-VAR.                               PROG01  "
After:   "     MOVE 'ABC' TO WS-VAR."

3ステップをまとめて実行する

3つのステップを順番に適用する処理をまとめると次のようになる。

Python — 3ステップ適用
def normalize_phase1(source: str) -> list[str]:
    """
    Phase 1: 行ラベル除去 → コメント削除 → 識別領域除去
    source: ソースファイル全体の文字列
    戻り値: 正規化後の行リスト
    """
    lines = source.splitlines()

    # Step 1: 行ラベル除去
    lines = remove_sequence_numbers(lines)

    # Step 2: コメント行削除(標識列が * または /)
    lines = remove_comments(lines)

    # Step 3: 識別領域除去(末尾空白も除去)
    lines = remove_identification_area(lines)

    return lines


# ── 動作確認 ────────────────────────────────────────────────────────
sample_source = """\
000100 IDENTIFICATION DIVISION.
000200* このプログラムはサンプルです
000300/ ページ区切り
000400 PROGRAM-ID. SAMPLE.
000500 DATA DIVISION.
000600 WORKING-STORAGE SECTION.
000700 01 WS-MSG     PIC X(30).                                         PROG01
000800 PROCEDURE DIVISION.
000900     DISPLAY WS-MSG.
"""

result = normalize_phase1(sample_source)
for line in result:
    print(repr(line))
実行結果
' IDENTIFICATION DIVISION.'
' PROGRAM-ID. SAMPLE.'
' DATA DIVISION.'
' WORKING-STORAGE SECTION.'
' 01 WS-MSG     PIC X(30).'
' PROCEDURE DIVISION.'
'     DISPLAY WS-MSG.'

コメント行2行が削除され、識別領域 PROG01 も除去されていることが確認できる。

次の章では…

PART 04 では列7が -継続行 を前行に結合する処理を実装する。 文字列リテラルが複数行にまたがる場合の引用符の扱いが重要なポイントだ。

→ PART 04 — 継続行の結合へ