배경
현업에서 개발 중인 안드로이드 기반 추천 시스템에서는 비즈니스 규칙을 유연하게 관리하기 위해 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 구조)을 활용해 여는 기호를 저장하고, 닫는 기호가 나올 때마다 짝이 맞는지 확인하는 것이다. 절차는 다음과 같다:
- 파일 전체를 문자 단위로 스캔
- 각 기호가 등장할 때마다 조건에 따라 스택에 삽입 또는 삭제
- 주석 영역은 사전 처리를 통해 무시 가능하도록 변환
- 최종적으로 스택이 비어 있어야 모든 기호가 균형을 이룬 것으로 판단
주석 처리 및 특수 패턴 제거
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 수준의 정교함은 없지만, 간단한 구문 오류를 빠르게 탐지할 수 있는 실용적인 도구가 된다. 파이썬의 간결한 리스트 연산과 정규식 기능 덕분에 자바 대비 빠르게 프로토타이핑이 가능했으며, 스택 구조의 응용은 구문 분석의 기초 원리를 잘 보여준다. 향후 확장으로는 키워드 철자 검사, 들여쓰기 규칙 검증 등을 추가할 수 있다.