Drools DRL 파일의 구문 오류를 탐지하는 파이썬 기반 검사 도구 개발 — 스택 자료구조 활용

배경

현업에서 개발 중인 안드로이드 기반 추천 시스템에서는 비즈니스 규칙을 유연하게 관리하기 위해 Drools 룰 엔진을 도입했다. Drools는 처음 접하는 기술이었으나, 기본적인 사용법은 비교적 직관적이었다. 하지만 Android Studio 환경에서 .drl 파일 편집 시 구문 강조나 실시간 오류 체크 기능이 부족해 괄호 매칭 실수((, ), {, })가 반복적으로 발생했다. 이를 해결하기 위해 간단한 구문 검사 스크립트를 제작하였으며, 이 글에서는 그 과정과 핵심 로직을 설명한다.

Drools DRL 파일 구조 개요

DRL(Drools Rule Language) 파일은 자바 문법과 유사한 형식을 가지며, 주로 다음 요소로 구성된다:

  • package: 논리적 패키지 이름 선언 (필수)
  • import: 외부 클래스 참조
  • rule 블록: rule로 시작하고 end로 종료되며, 내부에 when(조건부)과 then(실행부)을 포함
  • 주석: // 또는 /* */ 형태 지원

예시:

package com.example.rules

import java.util.List;
import com.sample.Order;

rule "Discount for VIP"
when
    order : Order(customer.isVip() == true)
then
    order.applyDiscount(0.1);
end

검사 목적 및 전략

목표는 .drl 파일 내에서 다음 기호들이 올바르게 쌍을 이루는지 확인하는 것이다:

  • 괄호: ()
  • 중괄호: {}
  • 쌍따옴표: ""

단, 주석 영역이나 문자열 리터럴 내부는 제외해야 하며, 오류 발생 시 대략적인 줄 번호도 제공되어야 한다.

알고리즘 설계: 스택을 이용한 균형 검사

기본 아이디어는 스택(LIFO 구조)을 활용해 여는 기호를 저장하고, 닫는 기호가 나올 때마다 짝이 맞는지 확인하는 것이다. 절차는 다음과 같다:

  1. 파일 전체를 문자 단위로 스캔
  2. 각 기호가 등장할 때마다 조건에 따라 스택에 삽입 또는 삭제
  3. 주석 영역은 사전 처리를 통해 무시 가능하도록 변환
  4. 최종적으로 스택이 비어 있어야 모든 기호가 균형을 이룬 것으로 판단

주석 처리 및 특수 패턴 제거

URL 내의 ://나 주석 내 기호를 오류로 잘못 인식하지 않도록 사전 정규식 치환이 필요하다:

  • :// → 임시 문자열로 치환 (예: ___URL___)
  • //.* → 공백으로 치환 (단일행 주석 제거)
  • /\*\*/ → 특정 마커(예: #)로 치환하여 추후 매칭 처리

파이썬 구현 코드

import re

def preprocess_drl_content(file_path):
    with open(file_path, 'r', encoding='utf-8') as f:
        content = f.read()
    
    # URL 프로토콜 방지
    content = re.sub(r'://', '___URL___', content)
    # 단일행 주석 제거
    content = re.sub(r'//.*', ' ', content)
    # 다중행 주석 시작/끝 마커로 변환
    content = re.sub(r'/\*', '#OPEN#', content)
    content = re.sub(r'\*/', '#CLOSE#', content)
    
    return content

def validate_brackets(text):
    stack = []
    line_num = 1

    for char in text:
        if char == '\n':
            line_num += 1
            continue

        # 주석 내부인지 확인
        if stack and len(stack) >= 2 and stack[-2] in ['#OPEN#', '#']:
            if char == '#' and stack[-2] == '#OPEN#':
                if text[text.find('#OPEN#') + 7:text.find('#OPEN#') + 13] == '#CLOSE#':
                    stack.pop()
                    stack.pop()
            continue

        if char in ['(', '{', '"', '#OPEN#']:
            stack.append(char)
            stack.append(line_num)

        elif char == ')':
            if stack and stack[-2] == '(':
                stack.pop()
                stack.pop()
            else:
                print(f"불일치: 줄 {line_num}에 불필요한 ')' 기호 존재")
                return False

        elif char == '}':
            if stack and stack[-2] == '{':
                stack.pop()
                stack.pop()
            else:
                print(f"불일치: 줄 {line_num}에 불필요한 '}}' 기호 존재")
                return False

        elif char == '"':
            if stack and stack[-2] == '"':
                stack.pop()
                stack.pop()
            else:
                stack.append('"')
                stack.append(line_num)

    if stack:
        print(f"미완성 기호 발견: 줄 {stack[-1]} 근처에 열린 기호 존재 - {stack[-2]}")
        return False

    print("✅ 모든 기호가 올바르게 매칭됨.")
    return True

# 실행 예제
if __name__ == "__main__":
    path = input("DRL 파일 경로를 입력하세요 (드래그 가능): ").strip().strip('"')
    cleaned = preprocess_drl_content(path)
    validate_brackets(cleaned)

테스트 결과

정상 파일 입력 시:

✅ 모든 기호가 올바르게 매칭됨.

의도적으로 )를 하나 더 추가했을 경우:

불일치: 줄 40에 불필요한 ')' 기호 존재

결론

이 스크립트는 IDE 수준의 정교함은 없지만, 간단한 구문 오류를 빠르게 탐지할 수 있는 실용적인 도구가 된다. 파이썬의 간결한 리스트 연산과 정규식 기능 덕분에 자바 대비 빠르게 프로토타이핑이 가능했으며, 스택 구조의 응용은 구문 분석의 기초 원리를 잘 보여준다. 향후 확장으로는 키워드 철자 검사, 들여쓰기 규칙 검증 등을 추가할 수 있다.

태그: drools python syntax-checking stack-data-structure regular-expressions

7월 5일 22:56에 게시됨