12 패턴
먼저 알아야 할 것: 5장(뷰), 4장(줄기).
이 장이 답하는 물음
- 글에서 모양으로 찾고 바꾸려면 무엇을 쓰는가?
- 왜 패턴은 상수여야 하는가?
ptn과rex는 언제 어느 것을 쓰는가?
12.1 작은 패턴: ptn
ptn 의 패턴은 Lua 와 같은 크기다: %d %a %s 같은 갈래, […] 집합, * + - ?, 그리고 갈무리 (…). 고르기(|)는 없다. 맞은 뒤 n ptn.capture 로 갈무리를 읽는다.
12-ptn.pnx
use io
use ptn
use str
rem Pull fields out of log lines with a small pattern.
const entry "^(%d+):(%d+) (%u+) (.*)$"
word show (line str)
line entry ptn.match or_else (e) cat ("no match: ", line) io.println return end drop
cat (3 ptn.capture, " at ", 1 ptn.capture, "h", 2 ptn.capture, ": ", 4 ptn.capture) io.println
end
"09:15 INFO server started" show
"10:02 WARN disk 91% full" show
"garbage" show
"a1b22c333" "%d+" ptn.matches do (m str) cat ("[", m, "]") io.print end each
"" io.println
"hello world" "(%w+)" "<%1>" ptn.replace io.println
출력
$ pnx 12-ptn.pnx
INFO at 09h15: server started
WARN at 10h02: disk 91% full
no match: garbage
[1][22][333]
<hello> <world>
패턴은 작은 조각을 늘어놓은 것이다. 자주 쓰는 조각은 이렇다(전체 목록은 표준 16.6).
| 조각 | 맞는 것 |
|---|---|
a 처럼 보통 글자 | 그 바이트 하나 |
. | 아무 바이트 하나 |
%d %a %s %w | 숫자, 글자, 공백, 글자나 숫자 하나. 대문자(%D …)는 그 밖의 것 |
%. %% | 점, % 그 자체 |
[ae] [a-z] [^0-9] | 넣은 것 가운데 하나, 범위 가운데 하나, 넣지 않은 것 하나 |
조각 뒤의 + * ? | 한 번 이상, 0 번 이상, 0 번이나 한 번 |
^ 와 $ | 패턴 처음에 쓰면 글의 처음, 끝에 쓰면 글의 끝에서만 |
( … ) | 맞은 부분을 갈무리한다. n ptn.capture 로 읽는다 |
패턴 하나씩 시험해 보면 조각의 뜻이 잘 보인다.
12-ptn-steps.pnx
use io
use ptn
word say (ok bool, what str) cat (ok if "yes " else "no " end, what) io.println end
"order 66" "%d+" ptn.is_match "%d+: a digit, one or more" say
"order 66" "^%d+" ptn.is_match "^%d+: digits at the start, and it starts with a letter" say
"order 66" "%a+ %d+$" ptn.is_match "%a+ %d+$: letters, a space, digits at the end" say
"grey" "gr[ae]y" ptn.is_match "gr[ae]y on grey: [ae] is a or e" say
"colour" "colou?r" ptn.is_match "colou?r on colour: ? makes the u optional" say
"x=" "x=%d*" ptn.is_match "x=%d* on x=: * allows none at all" say
"3.14" "%d%.%d" ptn.is_match "%d%.%d on 3.14: %. is a real point" say
출력
$ pnx 12-ptn-steps.pnx
yes %d+: a digit, one or more
no ^%d+: digits at the start, and it starts with a letter
yes %a+ %d+$: letters, a space, digits at the end
yes gr[ae]y on grey: [ae] is a or e
yes colou?r on colour: ? makes the u optional
yes x=%d* on x=: * allows none at all
yes %d%.%d on 3.14: %. is a real point
ptn.capture 가 읽는 것은 이 실행의 마지막 맞음이다. 맞은 적이 없는데 읽으면 결함 range 다. 다른 실행에서 맞힌 것은 보이지 않는다 — 그 글은 이미 사라졌을 수 있기 때문이다.
12-capture-none.pnx
use io
use ptn
word first_number (gives str)
1 ptn.capture rem no match in this run yet: there is nothing to read
end
first_number io.println
결함으로 멈춤
$ pnx 12-capture-none.pnx
12-capture-none.pnx:4: index out of range
12.2 정규 표현식: rex
rex 는 고르기, 횟수({n,m}), 낱말 경계(\b), 플래그((?i) 등)가 있는 정규 표현식이다. 되돌아가지 않는 방식으로 맞추므로 어떤 패턴도 글의 길이에 비례하는 시간 안에 끝난다. 패턴은 rex.rx 로 프로그램을 만들어 쓰고, matches 의 블록은 묶음을 매개변수로 받는다.
12-rex.pnx
use io
use rex
use str
rem Dates in any of two forms, rewritten to one.
const iso r"(\d{4})-(\d{2})-(\d{2})"
word demo ()
"due 2026-10-15, paid 2026-11-02" iso rex.rx r"\3.\2.\1" rex.replace io.println
"id=7, x=20; name=kim, n=3" r"(\w+)=(\d+)" rex.rx rex.matches do (whole str, key str, value str)
cat (key, " -> ", value) io.println
end each
"a , b;c ,d" r"\s*[,;]\s*" rex.rx rex.split do (p str) cat ("<", p, ">") io.print end each
"" io.println
end
demo
출력
$ pnx 12-rex.pnx
due 15.10.2026, paid 02.11.2026
id -> 7
x -> 20
n -> 3
<a><b><c><d>
맞은 곳마다 블록이 바꿀 글을 만들게 할 수도 있다.
12-replace-by.pnx
use io
use rex
use str
buffer out 128
word title (s str gives int or fail) rem capitalize every word
out clear
s r"\b[a-z]" rex.rx out do (w str gives str) w str.upper end rex.replace_by or_fail
end
word show () "the quick brown fox" title or_else (e) -1 end to n cat (out contents, " (", n, " letters)") io.println end
show
출력
$ pnx 12-replace-by.pnx
The Quick Brown Fox (4 letters)
찾는 낱말은 셋이 더 있다. rex.match 는 처음 맞은 부분을 주고 없으면 실패한다. rex.matches 는 맞은 부분마다 블록을 돌리는 줄기이고, 블록은 맞은 전체와 묶음마다 str 하나를 받는다. rex.split 은 맞은 부분으로 가른 조각의 줄기다. ptn 에도 같은 줄기 ptn.matches 가 있다.
12-rex-words.pnx
use io
use str
use rex
word demo ()
"order 66, aisle 7" r"\d+" rex.rx rex.match or_else (e) "none" end io.println
"no digits here" r"\d+" rex.rx rex.match or_else (e) "none" end io.println
"x=1, y=22" r"(\w)=(\d+)" rex.rx rex.matches do (whole str, name str, value str)
cat (name, " is ", value) io.println
end each
"a;b;;c" r";" rex.rx rex.split do (p str) cat ("[", p, "]") io.print end each
"" io.println
end
demo
출력
$ pnx 12-rex-words.pnx
66
none
x is 1
y is 22
[a][b][][c]
12-ptn-matches.pnx
use io
use ptn
word demo ()
"x=1, y=22, z=333" "%a=%d+" ptn.matches do (m str) cat ("<", m, ">") io.print end each
"" io.println
end
demo
출력
$ pnx 12-ptn-matches.pnx
<x=1><y=22><z=333>
| 토큰 | 그 뒤의 스택 | 일어난 일 |
|---|---|---|
"order 66, aisle 7" r"\d+" rex.rx | "order 66, aisle 7" re | 글과 패턴 프로그램. rex.rx 는 적재 때 만든다. |
rex.match | "66" | 처음 맞은 부분의 뷰. 없었다면 실패한다. |
or_else (e) "none" end | "66" | 성공: 건너뛴다. |
io.println | — | 찍는다. |
흔한 오해: 정규 표현식을 보통 문자열에 적는다
"\d+" 는 거절된다. 보통 문자열에서 \d 는 모르는 이스케이프이기 때문이다. 더 나쁜 것은 "\b" 다 — 백스페이스 문자가 되어 조용히 다른 뜻이 된다. 패턴은 날 문자열 r"…" 에 적는다.12-escape.pnx
use rex
word digits (s str gives bool) s "\d+" rex.rx rex.is_match end
거절
$ pnx 12-escape.pnx
12-escape.pnx:2:35: unknown escape; a raw string r"..." keeps backslashes as written
12.3 패턴은 상수다
ptn 의 패턴과 rex.rx 의 패턴은 리터럴이거나 const 여야 한다. 처리기가 적재 때 패턴을 검사하고 프로그램으로 만들기 위해서다. 틀린 패턴은 돌기 전에 거절된다.
12-pattern-variable.pnx
use rex
word find (s str, p str gives bool) s p rex.rx rex.is_match end
거절
$ pnx 12-pattern-variable.pnx
12-pattern-variable.pnx:2:41: rx takes a string literal or a const name; a pattern made while running is rex.compile
돌 때 만든 패턴이 필요하면 rex.compile 을 쓴다. 틀린 패턴은 실패(format)로 온다.
12-compile.pnx
use io
use rex
use env
word count_matches (pattern str, s str gives int or fail)
pattern rex.compile or_fail to re
0 to n
s re rex.matches do (m str) n 1 add to n end each
n
end
word main ()
1 env.arg or_else (e) "x" end to p
cat ("matches of ", p, ": ", p "a1 b22 c333" count_matches or_else (e) -1 end) io.println
"(" "a1" count_matches or_else (e) cat ("bad pattern: ", e err_text) io.println -1 end drop
end
main
출력
$ pnx 12-compile.pnx [0-9]+
matches of [0-9]+: 3
bad pattern: format
문답
물음. ptn 과 rex 는 어느 것을 쓰는가?
답. 짧은 패턴, 갈무리 몇 개면 ptn 이 가볍다. 고르기나 횟수가 필요하거나, 밖에서 온 패턴이거나, 아주 긴 글이면 rex 를 쓴다. ptn 은 되돌아가며 찾으므로 나쁜 패턴은 걸음의 한계에 걸려 결함으로 멈춘다.
문답
물음. 맞은 부분은 복사되는가?
답. 아니다. rex.match, 묶음, split 의 조각은 모두 원래 글을 가리키는 뷰다(5장). 바꾼 글만 새로 만들어지고, replace 는 구역에, replace_into 와 replace_by 는 버퍼에 쓴다.
정리
ptn은 Lua 크기의 패턴이고, 갈무리는ptn.capture로 읽는다. 그 실행에 맞음이 없으면 결함range(표준 16.6).rex는 되돌아가지 않는 정규 표현식이고, 묶음은 블록의 매개변수가 된다(16.7).- 패턴은 리터럴이나
const다. 돌 때 만드는 것은rex.compile(16.6, 16.7). - 정규 표현식은 날 문자열에 적는다(6.5).