JS Regex

regex에서 "또는 or"을 표현하기 위해 ``js [(bar)(foo)]`` 처럼 사용하면 ``()``로 묶어도 한 char 씩 잘라서 인식하기 때문에

 ``js ((bar)|(foo))``를 사용해야 한다.

()가 추가되기 때문에 캡쳐링 그룹 인덱스가 틀려질 수 있다는 점에 주의한다.


JS에서 정규 표현식은 보통 같은 문자열에 대해 반복해서 연산을 수행할 때 주목할 만한 성능상의 이점이 있다. 

그래서 ``indexOf``를 쓰는 것 보다는 정규 표현식을 쓰는 것이 좋다.


정규식은 inline 보다는 변수에 저장해서(stored) 사용하는 것이 좋고,

RegExp 객체를 사용하는 것 보다 literal을 사용하는 것이 좋다.

또한, 반복문 안에 넣으면 계속 컴파일 되는 경우가 있으므로 반복문 외부에 선언한다.


literal

```js

const re = /ab+c/;

```

스크립트가 로드되었을 때 정규식의 컴파일을 제공한다.

정규식이 계속 지속 될것이라면 이렇게 사용하는 것이 좋다.


RegExp 객체의 생성자 함수

```js

const re = new RegExp("ab+c");

```

정규식의 런타임 컴파일을 제공한다.

정규식 패턴이 바뀔 것이라고 알고 있거나, 패턴을 잘 모르거나, 사용자 입력과 같이 다른 소스로부터 패턴을 얻어 올 때에 생성자 함수를 쓰는 것이 좋은데, stored literal에 비해 많이 느리다.

역슬래시가 literal에서와는 조금 다른 의미로 보통 두 개의 역슬래시를 사용해야 하며 따옴표도 이스케이프시켜야 한다.


capturing group


`` = bar foo=``는 객체가 반환되기는 했으나 `` length == 0``

``js [a-z]*``가 0개이고 ``\s``가 붙는 것으로 잡혀서 객체가 반환되기는 된다.


``=bar=``는 객체가 반환되지 않는다.

``js ([a-z]*)\s``가 아예 없는 것으로 잡힌다.


중요한 것은 둘 다 false로 취급된다는 것. 

JS는 ``js null, undefined, NaN, 빈 문자열 '', 0, false``를 ``js false``로 본다.

* 이 외의 값은 모두 참이다.

해서 ``js test()``를 사용하지 않아도 ``js if(matches[1])``로 검사할 수 있다.


RegExp.$_ / $1...9

``RegExp.$_``          :  최근에 탐색한 문자열 전체를 저장하고 있다.

``RegExp.$1...$9``  :  가장 최근에 매치된 capturing group을 저장하고 있다. 


``js test()``를 사용했더라도, group으로 묶었다면 RegExp.$1 ~ $9에 저장된다.

또한 ``js exec()``의 반환값 ``[매칭된 부분 전체, captured group1, captured group2, ...]``은 

``[RegExp.$_, RegExp.$1, RegExp.$2, ...]`` 와 동일하다.


그래서 ``js exec()``대신 ``js text()``를 사용해도 구현상 문제는 없으며

``js text()``를 사용하는 것에 성능 이득을 보려면 캡쳐링 하지 않도록 ``(?:...)``을 지정해야 한다.


match()

capturing group을 사용하지 않는 경우 일반적으로 ``js match()``를 사용하는게 훨씬 편하다.
``js match()``를 사용할 때 ``g`` flag가 없다면 ``js exec()``와 동일하게 동작하게 되므로 ``js match()``를 사용하는 의미가 없어진다. 그래서 꼭 patten에 ``g`` flag를 설정해야 한다.

``js match()`` 메소드는 ``js exec()``와 달리 전체 문자열에서 매치되는 부분을 한 번에 Array로 반환해 줘서 반복 돌릴 필요가 없다는 장점이 있지만, captured groups을 반환하지 않는다.
return type도 ``Object``로 이루어진게 아닌 ``String``들로 이루어진 ``Array``다. 즉 matched substrings를 반환한다.
실제로 ``js match()``를 수행하고 ``RegExp``를 조사해 보면 ``RegExp.$_``는 존재하지만, ``RegExp.$1...9``는 비어있는 string으로 나온다.

그래서 matched substrings을 반환받는 동시에 captured groups도 반환받고 싶다면, ``js exec()``를 반복 호출해야 한다.


exec()

``g`` flag를 설정하는 경우 몇 가지 주의할 점이 있다.


```js

var re = /Hello/g;

var matches = re.exec("Hello world! Hello world2! Hello world3!);

```

직관적으로 생각해 보면 ``matches``에 ``[Hello, Hello, Hello]``가 반환될 것 같지만 아니다. 그건 ``js match()``를 사용했을 경우다.

``js exec()``가 반환하는 값은 항상 ``[매칭된 부분 전체, captured group1, captured group2, ...]`` 이다. 


뒤에 있는 Hello를 잡고 싶다면, global flag를 그대로 두고 ``js exec()``를 연속으로 호출해야 한다.

``g`` flag는 다음 패턴 검색을 위해 어디서 부터 패턴 매칭을 수행해야 하는지를 저장(``js RegExp.lastIndex``)해 두는 역할을 하기 때문에 ``js exec()``를 연속으로 호출하면 뒤에 있는 Hello가 차례로 검출된다. 


이상하게 동작하는 경우

이상하게 동작하는 경우는 보통 다음 두 가지 이유로 일어난다.

#1   각 ``js exec()``를 호출하는 사이에 문자열이 변경되는 경우

#2   각 ``js exec()``를 호출하는 사이에 정규표현식 객체를 재사용하는 경우.


정상적인 예제는 다음과 같다.



#1 각 exec()를 호출하는 사이에 문자열이 변경되는 경우

``js exec()``는 호출될 때 마다 매번 string에 접근하기 때문에 ``js replace``등으로 string을 변경하는 경우 주의해야 한다.
``g`` flag가 설정되어 있으면 어디까지 읽었는지 index를 저장해 놓았다가, 다음 ``js exec()`` 호출 시 거기서부터 읽어나가게 되는데, 
``js replace()``시 문자열의 각 문자의 index가 틀어질 수 있기 때문에 (문자열이 당겨지는 등) 조회가 안될 수 있다.


두 번째에 위치한 ``Hello world3``이 매치되지 않았다.

이 경우 ``g`` flag가 설정되어 있지 않으면 제대로 매치 되기는 한다. 즉, ``g`` flag가 없어야 제대로 global match 된다.
``g`` flag가 없는 경우 매번 string의 처음부터 매치를 수행하게 되는데 string이 수정되면 수정된 string을 대상으로 매칭을 수행하기 때문에 이전에 매치된 substring이 변경되어 더 이상 매치되지 않기 때문에 그 다음 매치를 찾아주게 되어 제대로 동작하는 것 처럼 보인다. 그러나 이렇게 사용하는건 좋지 못하다.


그래서 문자열을 변경해야 하는 경우, 다음과 같이 원본 문자열을 buffer에 넣어놓고 이를 대상으로 match 검사를 수행하는 것이 좋다.

```js

var match_buf = line;

var matches = re.exec(match_buf);

```


#2 각 exec()를 호출하는 사이에 정규표현식 객체를 재사용하는 경우.



``g`` flag가 있지만, 반복을 거듭해도 첫 번째 매치된 문자열만 계속 매치되며 무한 루프에 빠지게 된다.

이는 ``re``객체를 ``replace()``에서 재사용해서 어디까지 읽었는지 저장해놓은 index가 초기화되어, ``g`` flag가 없을 때 처럼 매번 처음부터 다시 매치하기 때문이다.


그래서 ``re``객체를 재사용하는 것이 아니라, 반환된 matched substring과 captured group을 사용해야 한다.

```js

line = line.replace(matches[0], "REP"+matches[1]);

```