Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
99 lines
3.6 KiB
Python
99 lines
3.6 KiB
Python
"""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
|