12 패턴

먼저 알아야 할 것: 5장(뷰), 4장(줄기).

이 장이 답하는 물음

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 는 버퍼에 쓴다.

정리