Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
7.2 KiB
정의부(declaration) 수집 설계 — 2026-09-21
왜 필요한가
이 인덱스의 최종 용도는 비슷한 로직을 새로 짤 때 기존 코드를 찾아 복사해 오는 것이다.
그런데 인덱스에 담긴 조각(logic_chunk)의 코드 원문은 로직만이다. ABAP 은 내부테이블·스트럭처·
상수·필드심볼을 TOP 인클루드나 FORM 머리에 따로 선언하므로, 로직만 복사해 붙여넣으면
GT_LOAD 는 선언되지 않았습니다 류의 문법 오류가 난다.
그래서 정의부를 인덱싱 시점에 따로 수집한다. 조각 코드에는 계속 로직만 둔다(검색 품질을 지키기 위해서다 — 선언 수백 줄이 조각에 섞이면 조각의 뜻이 흐려진다).
수집한 정의부를 쓸지 말지는 판단하지 않는다. 붙여넣는 쪽의 LLM 이 정한다: 이미 같은 선언이 있으면 버리고, 이름이 겹치면 바꾸고, 타입만 빌려 쓰기도 한다. 인덱스는 "이 조각을 가져가려면 이것들이 함께 필요하다"는 재료와 분류만 정확히 넘긴다.
무엇을 수집하는가
parser/declarations.py 가 선언 문장을 붙여넣을 수 있는 한 문장 단위로 잘라 declaration
테이블에 넣는다 (index/db.py).
| 항목 | 내용 |
|---|---|
| kind | data / types / constants / tables / field-symbol / parameter / select-option / ranges / type-pool / controls / class / macro |
| scope | global(TOP·공용 인클루드) / unit(FORM·METHOD 머리의 로컬 선언) |
| code | 선언 원문 — 체인 항목이면 헤드를 다시 붙이고 , 를 . 로 닫아 유효한 문장으로 만든다 |
| depends | 이 선언이 참조하는 다른 이름 (LIKE GT_DATA → GT_DATA, TYPE TABLE OF TY_X → TY_X) |
세 가지가 핵심이다.
BEGIN OF … END OF는 한 덩어리다. 업무 프로그램의 내부테이블은 거의 전부 이 형태다 (DATA : BEGIN OF GT_UPLOAD OCCURS 0, … END OF GT_UPLOAD.).parser/dataflow.py의 심볼 추출은 이걸 건너뛰지만(추적에는 이름만 필요하다), 정의부에는 반드시 통째로 있어야 한다.- 체인 항목도 혼자 설 수 있어야 한다.
DATA: a TYPE i,\n b TYPE i.의b만 잘라내면b TYPE i.라 붙여넣을 수 없다 →DATA:\n b TYPE i.로 헤드를 복원한다. 이를 위해parser/statements.py가 체인 전개 시 항목별 줄 범위를 기록한다. - 로컬 클래스·매크로도 정의부다.
CLASS lcl_x DEFINITION … ENDCLASS,DEFINE … END-OF-DEFINITION은 unit 통째를 한 건으로 담는다.
조각 → 정의부 조립 (index/decls.py)
- 조각 코드의 식별자를 모은다 (파서 토크나이저 · 문장 단위)
declaration에서 찾는다 — unit 로컬 먼저, 없으면 전역- 찾은 선언의
depends를 재귀로 끌어온다 (LT_DATA LIKE GT_DATA→ GT_DATA) - 의존이 먼저 오도록(post-order) 정렬해
declaration_code한 덩어리로 만든다
결과는 네 갈래로 분류해 넘긴다.
| 필드 | 뜻 | 붙여넣는 쪽이 할 일 |
|---|---|---|
declarations / declaration_code |
함께 가야 하는 선언 원문 | 있으면 버리거나, 이름을 바꿔 쓰거나, 그대로 넣는다 |
declaration_external_refs |
DDIC 사전 객체 (BKPF, LVC_T_FCAT …) | 가져갈 게 없다. 대상 시스템에 있는지만 본다 |
declaration_params |
이 조각이 쓰는 FORM 파라미터 | 호출 측에서 넘어오는 값이다 |
declaration_unresolved |
변수처럼 생겼는데 선언을 못 찾은 이름 | 수집 안 된 인클루드·함수 인터페이스일 수 있다 |
판정에서 가른 두 가지
LIKE BSEG-WRBTR은 작업영역이 아니다. 사전에서 타입만 빌려 쓰는 것이라 TOP 의TABLES: BSEG.를 딸려 보내면 안 된다. 반면bseg-wrbtr = 100.처럼 작업영역을 실제로 쓰면 그 선언이 필요하다. 그래서 참조를 컴포넌트까지 보존해 둘을 구분한다.- 호출문의 형식 파라미터는 변수가 아니다.
CALL FUNCTION 'X' EXPORTING i_bukrs = gv_bukrs의i_bukrs와EXCEPTIONS no_rate_found는 호출 대상의 인터페이스다. 이름만 보면 변수와 구분되지 않아(둘 다I_/NO_) 문장 구조로 걸러낸다.
저장하지 않고 읽을 때 계산한다
조각↔선언 대응은 logic_chunk 에 캐시하지 않는다. declaration 과 조각 코드만 있으면
결정론적으로 다시 나오므로, 캐시하면 조각 재추출·선언 변경과 엇갈려 따로 늙기만 한다.
조각 하나당 수십 줄을 토큰화하는 비용이라 조회 경로에서 충분히 싸다.
실측 (샘플 55본 / 조각 3,212건, 2026-09-21)
- 선언 18,049건 수집 (data 11.7k · constants 1.7k · field-symbol 1.0k · tables 1.0k · type-pool 488 · macro 295 · class 91 …)
- 조각의 79% 가 정의부를 갖는다 (평균 3.5건, 최대 21건). 나머지 21%는 호출·제어·메시지처럼 프로그램 선언을 쓰지 않는 조각이다.
- 선언을 못 찾은 이름이 남은 조각 3% — DDIC 테이블(J_1BBRANCH), 함수 인터페이스 파라미터, 화면 필드가 대부분이다.
곁가지로 고친 파서 버그 3건
정의부를 조립하면서 "선언이 없다"고 새어 나온 이름들을 좇아가 드러난 것들이다.
CLASS lcl_x DEFINITION DEFERRED.를 블록으로 열어 그 뒤 인클루드 전체를 CLASS_DEF 하나로 삼켰다 (ENDCLASS 가 없다). ZCO_ALV 의 선언 수십 건이 통째로 사라져 있었다.- FORM 시그니처의 파라미터를 첫 개만 잡았다 (
USING p_date LIKE x p_days LIKE y에서 TYPE/LIKE 를 만나면 멈췄다). PARAMETER(단수형)를 선언 키워드로 몰랐다 — 미인식 문장 123건 → 3건.
API
| Method | Path | 정의부 관련 |
|---|---|---|
| GET | /chunks/{chunk_id} |
declarations, declaration_code, declaration_external_refs, declaration_params, declaration_unresolved (끄려면 ?decls=false) |
| GET | /programs/{name}/units/{unit}/code |
같은 필드 (unit 통째로 가져갈 때) |
| GET | /programs/{name}/declarations?scope=global|unit |
프로그램의 선언 카탈로그 |
위키에서의 표시
원문은 프로그램 문서의 ## 정의부 섹션에 한 번만 싣는다 (인클루드별, 전역 선언 전체 +
조각이 실제로 쓰는 unit 로컬 선언). 조각 쪽에는 이름만 두고 그 항목으로 가는 문서 내 링크를 건다.
- 정의부(복사 시 함께 필요): [`GT_LOAD`](#decl-zfic0020top-6-gt_load), [`C_X`](#decl-zficom-213-c_x)
…
#### GT_LOAD — data · L6-L56 {#decl-zfic0020top-6-gt_load}
{#id} 는 제목에 앵커를 다는 표기이고, 뷰어(wiki_out/viewer.html)가 이걸 id 로 바꿔
눌러서 이동하게 만든다(이동 후 1.4초 강조). 뷰어 전용 기능이 아니다 — 마크다운으로 읽으면
링크 텍스트가 곧 선언 이름이라 LLM 에게는 지금까지와 똑같이 읽히고, 앵커는 무시된다.
링크와 앵커가 어긋나면 조용히 죽으므로 짝을 테스트로 묶어 뒀다
(tests/test_wiki_out.py::test_chunk_links_to_declaration). 실측 11,051개 링크 / 깨진 링크 0개.