파서를 직접 구현하는 과정에서 가장 빈번하게 마주치는 문제 중 하나는 구문 오류가 발생했을 때 정확한 위치를 특정하지 못하는 상황이에요. 단순히 "Unexpected token"이나 "Syntax error"와 같은 모호한 메시지만 출력하게 되면, 사용자는 수백 줄의 코드 중에서 어느 부분이 잘못되었는지 찾아내기 매우 힘들어지거든요. 이를 해결하기 위해서는 파싱 단계 이전인 렉서(Lexer) 단계부터 각 토큰에 위치 정보를 부여하는 구조를 설계하는 것이 중요해요. 모든 토큰이 소스 코드의 어느 줄과 몇 번째 칸에서 생성되었는지 기록을 남기는 방식은 실무에서 매우 강력한 도구로 활용되거든요.
interface Token {
type: string;
value: string;
line: number;
column: number;
}
function createToken(type: string, value: string, line: number, column: number): Token {
return { type, value, line, column };
}
// 예시: 10번째 줄 5번째 칸에 위치한 세미콜론 토큰
const token = createToken("SEMICOLON", ";", 10, 5);
console.log(token);
구체적인 구현 방법은 토큰을 정의하는 데이터 구조에 line과 column 필드를 포함하는 방식이에요. 렉서가 소스 코드를 한 글자씩 읽어 내려가며 토큰를 생성할 때, 현재 위치의 줄 번호와 열 번호를 실시간으로 계산하여 토큰 객체에 포함하도록 설계해요. 예를 들어 줄 바음 문자를 만날 때마다 line 변수를 1씩 증가시키고, 공백이나 탭을 만날 때마다 column 변수를 적절히 조정하는 방식이죠. 이렇게 정교하게 관리된 위치 정보는 파서가 문법을 검증하는 과정에서 핵심적인 역할을 수행하게 돼요. 파서는 단순히 문법이 틀렸다고 말하는 대신, 10번째 줄의 5번째 칸에서 문제가 발생했다는 식으로 구체적인 위치를 명시할 수 있게 되거든요.
function reportError(message: string, token: Token) {
console.error(`Error: ${message} at line ${token.line}, column ${token.column}`);
}
// 파서 내부에서 실제 오류 발생 시 호출
function parseExpression(token: Token) {
if (token.type !== "EXPECTED_TYPE") {
reportError("Unexpected token type", token);
}
}
이러한 위치 정보는 단순히 에러 메시지를 시각적으로 개선하는 용도에만 그치지 않아요. 나중에 컴파일러나 인터프리터를 구축할 때 디버깅 정보를 생성하거나, 소스 맵을 생성하여 트랜스파일링된 코드의 위치를 추적하는 등 다양한 단계에서 필수적인 데이터로 활용되거든요. 파싱 단계에서부터 체계적으로 위치 정보를 추적하는 구조를 설계해두면, 프로젝트 규모가 커지거나 기능이 확장될 때도 일관성 있는 에러 보고 체계를 유지할 수 있어 개발자 경험을 크게 향상시킬 수 있어요. 결국 좋은 도구는 사용자가 직면한 문제를 정확하게 안내하는 것에서부터 시작된다고 볼 수 있어요.
'develop > Parser' 카테고리의 다른 글
| Abstract Syntax Tree(AST) 노드 구조 설계 (Parser) (0) | 2026.10.06 |
|---|---|
| 재귀 하강 파서 내 문맥 의존적 토큰 처리 (Context-Sensitive Token Parsing) (0) | 2026.09.29 |
| Pratt Parsing 기반 표현식 파싱 구현 (0) | 2026.09.12 |
| 재귀 하강 파서 내 룩어헤드(Lookahead) 구현 (LL(k)) (0) | 2026.09.03 |
| 멀티라인 문자열 및 이스케이프 시퀀스 처리 (Lexer) (0) | 2026.09.02 |