LHJ

I'm a FE developer.

17.19 룩어헤드

31 May 2020 » js_lj

17.19 룩어헤드

적극적 일치와 소극적 일치를 이해하면 정규식 전문가가 될 수 있다고 했습니다.
여기에 더해 룩어헤드(lookahead)까지 이해하면 정규식을 마스터했다고 해도 과언이 아닙니다.
룩어헤드는 앵커나 단어 경계와 마찬가지로 입력을 소비하지 않습니다.
하지만 룩어헤드는 하위 표현식도 소비하지 않고 찾을 수 있으므로, 앵커와 단어 경계보다 범용적으로 쓸 수 있습니다.
단어 경계에서 ‘다시 넣는’ 방법을 고민할 필요가 없는 특징 역시 룩어헤드에도 적용됩니다.
룩어헤드는 문자열이 겹치는(overlapping) 상황에 필요하고, 룩어헤드를 써서 단순화시킬 수 있는 패턴이 많습니다.

룩어헤드를 설명할 때 주로 사용하는 예제는 비밀번호가 규칙에 맞는지 검사하는 겁니다.
예제가 너무 복잡하면 곤란하니, 비밀번호에는 대문자와 소문자, 숫자가 최소한 하나씩 포함되어야 하고 글자도 아니고 숫자도 아닌 문자는 들어갈 수 없다고 합시다.
물론 정규식을 여러 개 쓰는 방법으로 해결할 수 있습니다.

function validPassword(p) {
    return /[A-Z]/.test(p) &&       // 대문자가 최소한 하나
        /[a-z]/.test(p) &&          // 소문자가 최소한 하나
        /[0-9]/.test(p) &&          // 숫자가 최소한 하나
        !/[^a-zA-Z0-9]/.test(p);    // 영문자와 숫자만 허용
}

이 정규식을 하나로 묶고 싶으면 어떻게 해야 할까요?
우선 다음과 같은 방법을 떠올릴 수 있겠지만, 원하는 대로 동작하지는 않습니다.

function validPassword(p) {
    return /[A-Z].*[0-9][a-z]/.test(p);
}

이 정규식은 대문자가 맨 앞에 있어야 하고 숫자와 소문자로 끝나야 하는 등 우리의 의도와는 다른 규칙을 강요할 뿐더러, 잘못된 문자가 있는지 체크하지도 않습니다.
정규식이 진행하면서 문자를 소비하므로, 사실 지금까지 배운 방법으로는 불가능하기도 합니다.

룩어헤드는 입력을 소비하지 않으므로 이런 경우에 쓰기 적합합니다.
사실 각 룩어헤드는 입력을 소비하지 않는 독립적 정규식입니다.
자바스크립트의 룩어헤드는 (?=[subexpression]) 형태입니다.
하위 표현식 뒤에 이어지지 않는 것만 찾는 부정형 룩어헤드 (?![subexpression]) 도 있습니다.
룩어헤드를 쓰면 정규식 하나로 비밀번호의 유효성을 검사할 수 있습니다.

function validPassword(p) {
    return /(?=.*[A-Z])(?=.*[0-9])(?=.*[a-z])(?!.*[^a-zA-Z0-9])/.test(p);
}

이 외계어 같은 정규식을 보면 차라리 정규식 여러 개를 쓰는 함수가 낫겠다 싶기도 할 겁니다.
최소한 읽기는 쉬우니까요.
그리고 이 예제만 보면 사실 필자도 그 의견에 동의합니다.
하지만 이 예제는 룩어헤드와 부정형 룩어헤드의 중요한 사용법을 잘 보여줍니다.
룩어헤드는 분명 고급 정규식에 속하지만, 특정한 타입의 문제를 해결할 때는 아주 유용하게 쓸 수 있습니다.