Initial commit: ABAP indexing pipeline (ingest, parser, summarize, index, query, wiki)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
byeongwook.choi
2026-09-21 13:23:37 +09:00
co-authored by Claude Fable 5.1
commit 11ae3629b2
453 changed files with 259183 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
"""ABAP 라인 → (코드, 주석) 분리와 토큰화.
목표는 완전한 ABAP 렉서가 아니라 (계획서 §11.3) 문장 패턴 매칭에 충분한 토큰열이다.
- 줄 첫 문자 '*' → 전체 주석 줄
- 코드 중 '"' 부터 줄 끝 → 인라인 주석 (단, '...' / `...` / |...| 리터럴 내부 제외)
- 식별자는 -, ->, =>, / 를 포함해 한 토큰으로 취급한다
(gs_head-belnr, zcl_x=>meth, lo_obj->meth, TEXT-004, /bic/xxx 등)
- 필드심볼도 컴포넌트까지 한 토큰이다 (<ls_fcat>-fieldname). 이걸 쪼개면 일반 대입
판정(up[1] == "=")이 깨져 쓰기 지점이 누락되고 미인식 문장으로 잡힌다 — ALV 필드카탈로그를
채우는 관용구라 프로그램에 따라 미인식률을 12% 까지 끌어올렸다.
"""
from __future__ import annotations
import re
from dataclasses import dataclass, field
@dataclass
class Line:
no: int # 1-based
code: str # 주석 제거된 코드 부분
comment: str # 이 줄의 주석 텍스트 (전체주석/인라인 모두)
is_full_comment: bool
_TOKEN_RE = re.compile(
r"""
'(?:[^']|'')*' # '문자열' ('' 이스케이프)
| `[^`]*` # `문자열`
| \|[^|]*\| # |템플릿|
| <[A-Za-z_][A-Za-z0-9_]*>(?:->|=>|[A-Za-z0-9_~/-])* # <필드심볼>[-컴포넌트 | ->메서드]
| &[0-9]+ # 매크로(DEFINE) 치환 파라미터 &1 &2 …
| [A-Za-z_/](?:->|=>|[A-Za-z0-9_~/-])*\(? # 식별자(-, ->, =>, /, ~ 포함; 단독 = 는 제외), 직결 여는괄호 허용
| [0-9]+
| ##[A-Za-z_]+ # pragma
| . # 그 외 1문자 ( ) . , : = 등
""",
re.VERBOSE,
)
def split_comment(line: str) -> tuple[str, str, bool]:
"""한 줄을 (코드, 주석, 전체주석여부)로 분리."""
if line.startswith("*"):
return "", line[1:].strip(), True
code_chars: list[str] = []
i, n = 0, len(line)
while i < n:
ch = line[i]
if ch == "'":
j = i + 1
while j < n:
if line[j] == "'":
if j + 1 < n and line[j + 1] == "'":
j += 2
continue
break
j += 1
code_chars.append(line[i : min(j + 1, n)])
i = j + 1
elif ch == "`":
j = line.find("`", i + 1)
j = n - 1 if j < 0 else j
code_chars.append(line[i : j + 1])
i = j + 1
elif ch == "|":
j = line.find("|", i + 1)
j = n - 1 if j < 0 else j
code_chars.append(line[i : j + 1])
i = j + 1
elif ch == '"':
return "".join(code_chars), line[i + 1 :].strip(), False
else:
code_chars.append(ch)
i += 1
return "".join(code_chars), "", False
def tokenize_code(code: str) -> list[str]:
tokens = []
for m in _TOKEN_RE.finditer(code):
t = m.group(0)
if t.strip():
# 식별자에 붙은 여는 괄호는 분리한다: "meth(" → "meth", "("
if len(t) > 1 and t.endswith("(") and not t.startswith(("'", "`", "|")):
tokens.append(t[:-1])
tokens.append("(")
else:
tokens.append(t)
return tokens
def read_lines(text: str) -> list[Line]:
out: list[Line] = []
for i, raw in enumerate(text.split("\n"), start=1):
code, comment, full = split_comment(raw.rstrip("\r"))
out.append(Line(no=i, code=code, comment=comment, is_full_comment=full))
return out