16 표준 모듈
(1) 표준 모듈은 처리기에 들어 있다. use 모듈 로 열고 모듈.낱말 로 쓴다(13.1). 표준 모듈의 낱말은 호스트의 허락 없이 바깥과 닿지 못한다 — 파일도, 시계도, 환경도 쓰지 않는다.
(2) 표준 모듈의 낱말은 새 저장소를 만들지 않는다. 결과는 다음 가운데 하나다.
- 받은 바이트를 가리키는 뷰. 복사가 없다.
- 구역(12.2)에 새로 쓴 바이트의 뷰.
- 부른 쪽이 준 버퍼에 붙인 바이트. 이런 낱말은 이름이
_into로 끝나거나 버퍼를 받고, 자리가 모자라면err.full로 실패한다.
16.1 str — 문자열
(1) 모듈 str 은 문자열을 바이트로 다룬다. 바이트의 번호는 0 부터 센다. 글자 단위로 다루는 낱말 (chars char_count is_utf8)만 UTF-8 로 읽는다. 대소문자 바꾸기(upper lower)는 ASCII 만 바꾼다.
(2) 구분자나 찾는 것을 받는 자리(sep, part)에는 str 이나 문자 하나(int, 6.5)를 줄 수 있다.
16.1.1 보기와 자르기
(3) 결과는 모두 받은 문자열의 뷰다.
| 낱말 | 서명 | 뜻 |
len is_empty | (str gives int) (str gives bool) | 바이트 수, 비었는가 |
take skip | (s str, n int gives str) | 앞의 n 바이트, 앞의 n 바이트를 뺀 나머지. 모자라면 있는 만큼 |
take_last skip_last | (s str, n int gives str) | 뒤의 n 바이트, 뒤의 n 바이트를 뺀 앞 |
slice | (s str, start int, n int gives str) | start 부터 n 바이트. 범위 밖이면 결함 range |
trim trim_left trim_right | (str gives str) | 앞뒤의 공백(스페이스, 탭, CR, LF)을 뺀 뷰 |
cut | (s str, sep gives str, str) | 처음 sep 의 앞과 뒤. sep 이 없으면 s 와 빈 글 |
try_cut | (s str, sep gives str, str or fail) | cut 과 같으나 sep 이 없으면 err.not_found |
span | (s str, class int gives str, str) | 앞에서 그 갈래의 바이트가 이어진 부분과 나머지 |
(4) span 의 갈래는 상수 str.digit str.alpha str.alnum str.space str.upper_case str.lower_case str.hex_digit str.punct 다(ASCII).
16-str-views.pnx
use io
use str
word show (label str, s str) cat (label, " [", s, "]") io.println end
word demo ()
" key = value " to line
"trim" line str.trim show
"trim_left" line str.trim_left show
"take 5" "hello, world" 5 str.take show
"skip 7" "hello, world" 7 str.skip show
"take_last 5" "hello, world" 5 str.take_last show
"skip_last 7" "hello, world" 7 str.skip_last show
"take 99" "short" 99 str.take show rem n beyond the end: what is there
"slice 7 3" "hello, world" 7 3 str.slice show
"key = value" " = " str.cut to (k, v)
"cut, before" k show
"cut, after" v show
"a=b=c" '=' str.cut to (k2, v2) rem the separator may be a character
"cut at a char" v2 show
"2026-10-15" str.digit str.span to (year, rest)
"span digits" year show
"span rest" rest show
end
demo
출력
$ pnx 16-str-views.pnx
trim [key = value]
trim_left [key = value ]
take 5 [hello]
skip 7 [world]
take_last 5 [world]
skip_last 7 [hello]
take 99 [short]
slice 7 3 [wor]
cut, before [key]
cut, after [value]
cut at a char [b=c]
span digits [2026]
span rest [-10-15]
16.1.2 찾기와 견주기
(5)
| 낱말 | 서명 | 뜻 |
starts_with ends_with contains | (s str, part gives bool) | |
find find_last | (s str, part gives int or fail) | 처음, 마지막으로 나온 자리. 없으면 err.not_found |
compare | (str, str gives int) | 바이트 차례로 견주어 음수, 0, 양수 |
byte_at | (s str, i int gives u8) | 바이트 i. 범위 밖이면 결함 range |
char_count | (str gives int) | UTF-8 글자의 수 |
is_utf8 | (str gives bool) | 올바른 UTF-8 인가 |
eq ne lt 등 비교 낱말도 문자열을 바이트 차례로 견준다(15.2).
16-str-search.pnx
use io
use str
word yes (b bool gives str) b if "yes" else "no" end end
"hello.pnx" ".pnx" str.ends_with yes io.println
"hello.pnx" "he" str.starts_with yes io.println
"hello.pnx" "lo." str.contains yes io.println
"a,b,c,b" ",b" str.find or_else (e) -1 end str.to_str io.println
"a,b,c,b" ",b" str.find_last or_else (e) -1 end str.to_str io.println
"a,b,c,b" "x" str.find or_else (e) -1 end str.to_str io.println
"abc" "abd" str.compare str.to_str io.println
"hello" 1 str.byte_at str.to_str io.println
"hello" str.len str.to_str io.println
"" str.is_empty yes io.println
출력
$ pnx 16-str-search.pnx
yes
yes
yes
1
5
-1
-1
101
5
yes
16.1.3 새 글 만들기
(6) 결과는 구역에 쓴 새 바이트다. 구역이 모자라면 결함 full 이다(12.2). 수로 읽는 낱말은 실패할 수 있다.
| 낱말 | 서명 | 뜻 |
upper lower | (str gives str) | ASCII 대소문자 바꾸기 |
repeat | (s str, n int gives str) | n 번 이은 글. n 이 음수면 결함 range |
replace | (s str, old str, new str gives str) | 모든 old 를 new 로 |
pad_left pad_right | (s str, width int gives str) | 스페이스로 채워 width 바이트로. 이미 길면 그대로. width 가 음수이거나 232 이상이면 결함 range |
to_str | (int gives str) | 십진 글. dec(N) 은 점 아래 N 자리까지(7.5) |
to_dec | (s str, N gives dec(N) or fail) | 십진 소수로 읽기: 부호, 숫자, 점과 그 아래 N 자리까지(N 은 상수). 자리가 더 많거나 다른 꼴이면 err.format |
to_int | (str gives int or fail) | 십진으로 읽기. 부호 하나와 숫자만 받는다(공백도 안 된다). 아니거나 64 비트를 넘으면 err.format |
to_int_base | (s str, base int gives int or fail) | 2–36 진법으로 읽기. 그 밖의 base 는 결함 range |
16-str-make.pnx
use io
use str
"Hello, World" str.upper io.println
"Hello, World" str.lower io.println
"ab" 3 str.repeat io.println
"one two one" "one" "1" str.replace io.println
cat ("[", "7" 4 str.pad_left, "]") io.println
cat ("[", "7" 4 str.pad_right, "]") io.println
-42 str.to_str io.println
"ff" 16 str.to_int_base or_else (e) -1 end str.to_str io.println
" 12" str.to_int or_else (e) -1 end str.to_str io.println
"12x" str.to_int or_else (e) -1 end str.to_str io.println
"한글" str.len str.to_str io.println
"한글" str.char_count str.to_str io.println
"\xff" str.is_utf8 if "utf-8" else "not utf-8" end io.println
출력
$ pnx 16-str-make.pnx
HELLO, WORLD
hello, world
ababab
1 two 1
[ 7]
[7 ]
-42
255
-1
-1
6
2
not utf-8
16.1.4 줄기와 버퍼에 쓰기
(7) 줄기(10.6)를 내는 낱말:
s sep str.split—sep으로 가른 조각. 빈 조각도 낸다.sep은str이거나 바이트 하나(u8상수, 보통 문자 리터럴)다.cut과try_cut의sep도 같다.s str.words— 공백으로 가른 조각. 빈 조각은 내지 않는다.s str.lines— 줄. 줄 끝의 LF 와 CR LF 는 빼고 낸다.s str.bytes— 바이트(u8).s str.chars— UTF-8 글자 하나씩의 뷰(str).
(8) 버퍼에 쓰는 낱말:
sep buf 줄기 str.join_into— 줄기의 조각을sep으로 이어 버퍼에 붙인다(or fail). 줄기는 바로 뒤의 낱말이 받아야 하므로 줄기를 맨 뒤에 둔다.s old new buf str.replace_into— 바꾼 글을 버퍼에 붙이고 바꾼 횟수를 준다(gives n int or fail).
16-str-stems.pnx
use io
use str
"one two three" str.words do (w str) cat ("<", w, ">") io.print end each
"" io.println
"a,b,,c" ',' str.split do (w str) cat ("<", w, ">") io.print end each
"" io.println
text END
first line
second line
END
str.lines do (l str) cat ("line: ", l) io.println end each
"hi" str.bytes do (b u8) b str.to_str io.print " " io.print end each
"" io.println
"가a!" str.chars do (c str) cat ("<", c, " ", c str.len, ">") io.print end each
"" io.println
buffer out 64
", " out "x y z" str.words str.join_into or_else (e) end
out contents io.println
out clear
word swap_dashes () "a-b-c" "-" "+" out str.replace_into or_else (e) 0 end to n cat (out contents, " (", n, " replaced)") io.println end
swap_dashes
출력
$ pnx 16-str-stems.pnx
<one><two><three>
<a><b><><c>
line: first line
line: second line
104 105
<가 3><a 1><! 1>
x, y, z
a+b+c (2 replaced)
제약
(9) str 의 낱말에 str 가 아닌 값을 주면 거절된다.
16-str-type.pnx
use str
42 str.upper drop
거절
$ pnx 16-str-type.pnx
16-str-type.pnx:2:4: this word takes a str
결함
(10) slice 와 byte_at 의 범위 밖 번호는 결함이다. 모자라도 되는 자르기는 take skip 을 쓴다.
16-str-slice-range.pnx
use io
use str
"hello" 3 5 str.slice io.println
결함으로 멈춤
$ pnx 16-str-slice-range.pnx
16-str-slice-range.pnx:3: index out of range
16-str-byte-range.pnx
use str
"hello" 5 str.byte_at drop
결함으로 멈춤
$ pnx 16-str-byte-range.pnx
16-str-byte-range.pnx:2: index out of range
16.2 fmt — 수를 글로
(1) cat 은 int 를 십진으로 넣는다(9.8). 다른 모양은 모듈 fmt 의 낱말로 str 을 만든다. 형식을 적는 작은 언어(%08x 같은 것)는 없다. 결과는 구역에 쓴다.
| 낱말 | 서명 | 뜻 |
hex bin | (n gives str) | 십육진(소문자), 이진. n 은 int 나 좁은 정수(7.6): int 의 음수는 64 비트 2 의 보수로, 좁은 정수는 그 폭의 비트로 |
hex_pad | (n, width int gives str) | 십육진을 앞에 0 을 채워 width 자리로. n 은 hex 와 같다 |
zero_pad | (n, width int gives str) | 십진을 앞에 0 을 채워 width 자리로. 부호도 자리를 차지한다. n 은 int 나 좁은 정수(u64 는 부호 없이) |
append_hex | (n, buf or fail) | 십육진을 버퍼에 붙인다. n 은 hex 와 같다 |
십진은 str.to_str, 버퍼에 붙이는 십진은 append_int(15.3)다. hex_pad 와 zero_pad 의 width 가 음수이거나 232 이상이면 결함 range, 구역이 모자라면 결함 full 이다.
16-fmt.pnx
use io
use fmt
255 fmt.hex io.println
5 fmt.bin io.println
255 8 fmt.hex_pad io.println
7 3 fmt.zero_pad io.println
-7 3 fmt.zero_pad io.println
buffer out 32
"0x" out append or_else (e) end
48879 out fmt.append_hex or_else (e) end
out contents io.println
출력
$ pnx 16-fmt.pnx
ff
101
000000ff
007
-07
0xbeef
제약
(2) fmt 의 낱말은 int 를 받는다. 다른 타입의 값은 거절된다. dec(N) 은 str.to_str 과 cat 이 적는다(7.5).
16-fmt-type.pnx
use fmt
use io
"abc" fmt.hex io.println
거절
$ pnx 16-fmt-type.pnx
16-fmt-type.pnx:3:7: this word takes an int or a narrow integer
결함
(3) hex_pad 와 zero_pad 의 width 가 음수이거나 232 이상이면 결함 range 다.
16-fmt-width.pnx
use fmt
use io
255 -1 fmt.hex_pad io.println
결함으로 멈춤
$ pnx 16-fmt-width.pnx
16-fmt-width.pnx:3: index out of range
(4) 결과가 구역에 들어가지 않으면 결함 full 이다. 아래의 폭은 허용되는 수이지만 구역보다 크다.
16-fmt-full.pnx
use fmt
use str
use io
7 4294967295 fmt.zero_pad str.len str.to_str io.println
결함으로 멈춤
$ pnx 16-fmt-full.pnx
16-fmt-full.pnx:4: a declared capacity is used up
진단
| 조건 | 메시지 |
|---|---|
int 가 아닌 값 | this word takes an int |
16.3 hash — 해시
(1) 모듈 hash 는 바이트의 해시를 int 로 준다. 암호에 쓰는 해시는 없다 — 그런 것은 호스트 낱말로 준다.
| 낱말 | 서명 | 뜻 |
crc32 | (str gives u32) | CRC-32 (IEEE 802.3, zlib 과 같음) |
fnv1a | (str gives u64) | 64 비트 FNV-1a |
hash64 | (str gives u64) | 키가 0 인 SipHash-2-4 |
16-hash.pnx
use io
use hash
use fmt
"hello" hash.crc32 fmt.hex io.println
"hello" hash.fnv1a fmt.hex io.println
"hello" hash.hash64 fmt.hex io.println
출력
$ pnx 16-hash.pnx
3610a686
a430d84680aabd0b
8cc15d5db2f752b9
(1a) 검사 값: 빈 글의 crc32 는 0 이고, "123456789" 의 crc32 는 cbf43926 이다(CRC-32 의 표준 검사 값). 빈 글의 fnv1a 는 FNV 의 시작값 cbf29ce484222325 다. 다른 처리기는 같은 값을 내야 한다.
16-hash-vectors.pnx
use io
use str
use fmt
use hash
"" hash.crc32 str.to_str io.println
"123456789" hash.crc32 fmt.hex io.println
"" hash.fnv1a fmt.hex io.println
"" hash.hash64 fmt.hex io.println
출력
$ pnx 16-hash-vectors.pnx
0
cbf43926
cbf29ce484222325
1e924b9d737700d7
제약
(2) str 가 아닌 값의 해시는 거절된다. 수의 해시는 먼저 바이트로 만든다(16.5).
16-hash-type.pnx
use hash
42 hash.crc32 drop
거절
$ pnx 16-hash-type.pnx
16-hash-type.pnx:2:4: this word takes a str
16.4 encode — 바이트를 글로
(1) 모듈 encode 의 낱말은 모두 (s str, buf or fail) 이다. 바꾼 결과를 버퍼에 붙이고, 자리가 모자라면 err.full, 입력의 모양이 틀리면 err.format 으로 실패한다.
| 낱말 | 뜻 |
base64_encode base64_decode | RFC 4648 의 Base64. 풀 때 끝의 = 는 없어도 된다 |
hex_encode hex_decode | 바이트마다 십육진 두 자리. 쓸 때 소문자, 읽을 때 대소문자 모두 |
url_encode url_decode | RFC 3986 의 퍼센트 부호화. 문자, 숫자, - . _ ~ 밖의 바이트를 %XX 로 |
16-encode.pnx
use io
use encode
buffer out 64
word show (label str) cat (label, " ", out contents) io.println out clear end
word demo (or fail)
"Pinax!" out encode.base64_encode or_fail "base64" show
"UGluYXgh" out encode.base64_decode or_fail "decoded" show
"\x00\xff" out encode.hex_encode or_fail "hex" show
"a b&c=d/é" out encode.url_encode or_fail "url" show
"a%20b" out encode.url_decode or_fail "url decoded" show
end
demo or_else (e) cat ("failed: ", e err_text) io.println end
출력
$ pnx 16-encode.pnx
base64 UGluYXgh
decoded Pinax!
hex 00ff
url a%20b%26c%3Dd%2F%C3%A9
url decoded a b
16-encode-bad.pnx
use io
use encode
buffer out 16
"zz" out encode.hex_decode or_else (e) cat ("hex_decode: ", e err_text) io.println end
"ab*c" out encode.base64_decode or_else (e) cat ("base64_decode: ", e err_text) io.println end
출력
$ pnx 16-encode-bad.pnx
hex_decode: format
base64_decode: format
제약
(2) str 와 버퍼가 아닌 값을 주면 거절된다. 수를 바꾸려면 먼저 글이나 바이트로 만든다(16.1, 16.5).
16-encode-type.pnx
use encode
buffer out 8
5 out encode.hex_encode or_else (e) end
거절
$ pnx 16-encode-type.pnx
16-encode-type.pnx:3:7: this word takes a str and a buffer, as: s buf encode.base64_encode
진단
| 조건 | 메시지 |
|---|---|
str 와 버퍼가 아닌 값 | this word takes a str and a buffer, as: s buf encode.base64_encode |
16.5 bytes — 이진 자료
(1) 모듈 bytes 는 수와 글을 정해진 바이트 모양으로 버퍼에 쓰고, 바이트에서 읽는다. 쓰는 낱말은 (값, buf or fail) 이고(자리가 모자라면 err.full, 아무것도 쓰지 않는다), 읽는 낱말은 (s str gives 값, 나머지 이다(바이트가 모자라거나 모양이 틀리면
str or fail)err.format).
(2)
| 낱말 | 바이트 |
put_u8 put_u16 put_u32 put_u64put_i8 put_i16 put_i32 put_i64 | 이름이 말하는 타입의 값(7.6; put_i64 는 int). 작은 끝이 먼저 |
같은 이름 뒤에 _be | 큰 끝이 먼저 |
get_u8 … get_i64, get_u16_be … | 위의 짝. 이름이 말하는 타입의 값을 준다(get_i64 는 int) |
put_varint get_varint | LEB128. 값을 부호 없는 64 비트로 보고 7 비트씩 쓴다(음수는 10 바이트) |
put_str get_str | 길이(varint) 다음에 바이트 |
16-bytes.pnx
use io
use bytes
use encode
use str
buffer out 64
buffer hex 128
word dump () out contents hex encode.hex_encode or_else (e) end hex contents io.println hex clear end
word demo (or fail)
258 u16 out bytes.put_u16 or_fail rem little end first: 02 01
258 u16 out bytes.put_u16_be or_fail rem big end first: 01 02
300 out bytes.put_varint or_fail rem LEB128: ac 02
"hi" out bytes.put_str or_fail rem a varint length, then the bytes
dump
out contents to s
s bytes.get_u16 or_fail to (a, s1) rem the value and the rest
s1 bytes.get_u16_be or_fail to (b, s2)
s2 bytes.get_varint or_fail to (c, s3)
s3 bytes.get_str or_fail to (d, s4)
cat (a, " ", b, " ", c, " ", d, ", left ", s4 str.len) io.println
end
demo or_else (e) e err_text io.println end
출력
$ pnx 16-bytes.pnx
02010102ac02026869
258 258 300 hi, left 0
16.5.1 레코드
(3) 줄 buf bytes.pack 은 레코드(8.8)의 줄 하나를 필드의 차례대로 버퍼에 붙인다(or fail). s 줄 bytes.unpack 은 바이트에서 필드를 읽어 줄에 넣고 나머지 뷰를 준다(gives str or fail). 줄 bytes.packed_size 는 pack 이 쓸 바이트 수다.
(4) 필드의 바이트: 정수 폭은 그 폭으로(레코드에 big_endian 이 있으면 큰 끝이 먼저, 없으면 작은 끝이 먼저), int 는 8 바이트, bool 은 1 바이트, varint 는 LEB128, str(N) 은 길이(varint) 다음에 바이트, 줄을 담는 필드는 담긴 수(varint).
(5) pack 이 실패하면 버퍼는 부르기 전의 길이로 돌아간다. unpack 은 모든 필드를 읽을 수 있을 때만 줄에 쓴다 — 실패하면 줄은 그대로다.
16-bytes-pack.pnx
use io
use bytes
use encode
use str
record item
id u16
name str(8)
delta varint
end
record wire
big_endian
id u16
count u32
end
array items item 2
array wires wire 1
buffer out 64
buffer hex 128
word dump () out contents hex encode.hex_encode or_else (e) end hex contents io.println hex clear end
word demo (or fail)
0 items idx to a
513 u16 a to item.id "pinax" a to item.name -3 a to item.delta
a out bytes.pack or_fail
dump
cat ("packed_size ", a bytes.packed_size, ", written ", out contents str.len) io.println
1 items idx to b
out contents b bytes.unpack or_fail to rest
cat (b item.id, " ", b item.name, " ", b item.delta, ", rest ", rest str.len) io.println
"\x01\x02\x05" b bytes.unpack or_else (e) cat ("short input: ", e err_text) io.println "" end drop
cat ("the row is unchanged: ", b item.name) io.println
out clear
0 wires idx to w
258 u16 w to wire.id 7 u32 w to wire.count
w out bytes.pack or_fail
dump
end
demo or_else (e) e err_text io.println end
출력
$ pnx 16-bytes-pack.pnx
01020570696e6178fdffffffffffffffff01
packed_size 18, written 18
513 pinax -3, rest 0
short input: format
the row is unchanged: pinax
010200000007
제약
(6) pack unpack packed_size 는 레코드의 줄만 받는다.
16-bytes-pack-int.pnx
use bytes
buffer b 8
5 b bytes.pack or_else (e) end
거절
$ pnx 16-bytes-pack-int.pnx
16-bytes-pack-int.pnx:3:5: pack takes a row of a table of records and a buffer: row buf bytes.pack
결함
(7) 쓰는 값은 낱말 이름이 말하는 타입이다(put_u8 이면 u8, 7.6). int 는 먼저 그 타입으로 바꾸고(n u8), 범위 밖의 값은 거기서 결함 overflow 다. 자르지 않는다.
16-bytes-narrow.pnx
use bytes
buffer b 8
word store (n int) n u8 b bytes.put_u8 or_else (e) end end
300 store
결함으로 멈춤
$ pnx 16-bytes-narrow.pnx
16-bytes-narrow.pnx:3: integer overflow
n int = 300
16.6 ptn — 작은 패턴
(1) 모듈 ptn 은 Lua 의 패턴과 같은 크기의 패턴(pattern)으로 문자열을 찾고 바꾼다. 갈래 선택(|), 묶음의 되풀이, 역참조는 없다. 더 큰 문법은 rex(16.7)가 맡는다.
16.6.1 패턴의 글
(2) 패턴은 다음 항목의 나열이다. 바이트 단위로 맞춘다.
| 항목 | 맞는 것 |
| 보통 글자 | 그 바이트 |
. | 아무 바이트 |
%a %d %l %u %s %w %x %p %c | 글자, 숫자, 소문자, 대문자, 공백, 글자나 숫자, 십육진 숫자, 구두점, 제어 문자(ASCII). 대문자로 쓰면 그 밖 |
% 와 글자·숫자가 아닌 것 | 그 바이트 자체(%. %% %() |
[…] [^…] | 집합과 그 여집합. 안에 a-z 범위와 % 갈래를 쓴다. 여는 [ 나 [^ 바로 뒤의 ] 는 글자다 |
항목 뒤의 * + - ? | 0 번 이상(길게), 1 번 이상(길게), 0 번 이상(짧게), 0 번이나 1 번 |
( … ) | 갈무리(capture). 아홉까지 |
처음의 ^, 끝의 $ | 글의 처음, 글의 끝에 붙임 |
16-ptn-classes.pnx
use io
use ptn
"a1 B2_c3!" "%a" "." ptn.replace io.println rem letters
"a1 B2_c3!" "%d" "." ptn.replace io.println rem digits
"a1 B2_c3!" "%W" "." ptn.replace io.println rem not letter or digit
"a1 B2_c3!" "[%u_]" "." ptn.replace io.println rem a set: upper case or _
"aaab" "a-b" "<%0>" ptn.replace io.println rem - is the shortest repetition
"color colour" "colou?r" "#" ptn.replace io.println
출력
$ pnx 16-ptn-classes.pnx
.1 .2_.3!
a. B._c.!
a1.B2.c3.
a1 .2.c3!
<aaab>
# #
16.6.2 낱말
(3)
| 낱말 | 서명 | 뜻 |
is_match | (s str, pat gives bool) | 어디든 맞는 곳이 있는가 |
match | (s str, pat gives str or fail) | 처음 맞은 부분의 뷰. 없으면 err.not_found |
capture | (n int gives str) | 마지막 match 의 갈무리 n (1–9). 0 은 맞은 전체 |
matches | 줄기 | 맞은 부분을 차례로. 겹치지 않게 다음 자리에서 다시 찾는다 |
replace | (s str, pat, rep str gives str) | 맞은 곳을 모두 rep 로 바꾼 새 글. rep 의 %0–%9 는 갈무리, %% 는 % |
(4) ^ 로 시작하는 패턴은 처음 자리에서만 맞춰 본다. 그래서 matches 와 replace 도 많아야 한 번 맞는다.
(5) capture 가 읽는 마지막 맞음은 실행(작업)마다 따로 있다. 다른 match 를 부르면 바뀐다. 호스트가 시작한 실행은 맞음이 없는 채로 시작한다: 지난 실행의 맞음은 보이지 않는다. 그 갈무리는 지난 실행이 맞춘 글의 뷰이고, 그 글은 이미 없을 수 있기 때문이다.
16-ptn.pnx
use io
use ptn
const date "(%d+)-(%d+)-(%d+)"
word yes (b bool gives str) b if "true" else "false" end end
"abc 123" "%d+" ptn.is_match yes io.println
word demo ()
"on 2026-10-15 ok" date ptn.match or_else (e) "?" end io.println rem the whole match
cat (1 ptn.capture, "/", 2 ptn.capture, "/", 3 ptn.capture) io.println
"key = value" "^(%w+)%s*=%s*(%w+)$" ptn.match or_else (e) "?" end drop
cat (2 ptn.capture, " <- ", 1 ptn.capture) io.println
end
demo
"x=1, y=22, z=333" "%a=%d+" ptn.matches do (m str) cat ("[", m, "]") io.print end each
"" io.println
"2026-10-15" date "%3.%2.%1 (%0)" ptn.replace io.println
" trim me " "^%s*(.-)%s*$" "%1" ptn.replace io.println
"aaa" "^a" "-" ptn.replace io.println rem ^ anchors: one match, at the start
출력
$ pnx 16-ptn.pnx
true
2026-10-15
2026/10/15
value <- key
[x=1][y=22][z=333]
15.10.2026 (2026-10-15)
trim me
-aa
제약
(6) 패턴은 문자열 리터럴이거나 그런 값의 const 여야 한다. 적재 때 검사하기 위해서다. 돌 때 만든 글로 찾으려면 rex.compile(16.7)을 쓴다.
16-ptn-variable.pnx
use ptn
word f (s str, p str gives bool) s p ptn.is_match end
거절
$ pnx 16-ptn-variable.pnx
16-ptn-variable.pnx:2:38: a pattern is a string literal or a const name, so that it is checked at load
(7) 모양이 틀린 패턴(닫히지 않은 집합이나 갈무리, % 로 끝남, 열지 않은 ), 갈무리 열 개 이상)은 거절된다. %b %f, 역참조 %1, 위치 갈무리 () 도 거절된다.
16-ptn-bad.pnx
use ptn
word f (s str gives bool) s "[a-z" ptn.is_match end
거절
$ pnx 16-ptn-bad.pnx
16-ptn-bad.pnx:2:36: a set is not closed by ]
16-ptn-balance.pnx
use ptn
word f (s str gives bool) s "%b()" ptn.is_match end
거절
$ pnx 16-ptn-balance.pnx
16-ptn-balance.pnx:2:36: %b, %f and back references are not supported
결함
(8) capture 의 n 이 마지막 맞음에 없는 갈무리이거나 아직 맞음이 없으면 결함 range 다.
16-ptn-capture-none.pnx
use ptn
use io
word f () "ab" "(a)b" ptn.match or_else (e) "" end drop 2 ptn.capture io.println end
f
결함으로 멈춤
$ pnx 16-ptn-capture-none.pnx
16-ptn-capture-none.pnx:3: index out of range
(9) 맞추기는 되돌아가며 찾는다. 낱말 한 번에 224 걸음을 넘으면 결함 depth 로 멈춘다. 그런 패턴은 짧은 되풀이(-)를 줄이거나 rex 를 쓴다(rex 는 되돌아가지 않는다).
16-ptn-steps.pnx
use ptn
use str
"a" 300 str.repeat ".-.-.-.-.-.-.-.-x" ptn.is_match drop
결함으로 멈춤
$ pnx 16-ptn-steps.pnx
16-ptn-steps.pnx:3: call depth limit
진단
| 조건 | 메시지 |
|---|---|
| 패턴이 상수가 아님 | a pattern is a string literal or a const name, so that it is checked at load |
| 닫히지 않은 집합 | a set is not closed by ] |
%b %f 역참조 | %b, %f and back references are not supported |
16.7 rex — 정규 표현식
(1) 모듈 rex 는 정규 표현식(regular expression)으로 찾고 바꾼다. 맞추는 방법은 되돌아가지 않는 방식이라, 걸리는 시간이 글의 길이와 패턴의 크기의 곱을 넘지 않는다. 패턴을 적을 때는 날 문자열(6.5)을 쓴다 — 보통 문자열에서 "\d" 는 모르는 이스케이프로 거절되고, "\b" 는 백스페이스가 된다.
(2) use rex 가 있는 소스에서는 타입 이름 regex 가 보인다. regex 값은 만든 뒤에 고치지 못하는 패턴 프로그램이다. 지역 변수, 인자, 결과에만 쓴다.
16.7.1 패턴의 글
(3)
| 모양 | 뜻 |
| 글자 | 그 바이트들. 여러 바이트 글자는 그 바이트열 |
. | UTF-8 글자 하나. (?s) 가 없으면 줄바꿈은 뺀다 |
[abc] [a-z] [^…] | 글자 집합. 범위는 ASCII. 부정 집합은 UTF-8 글자 하나를 먹는다 |
\d \w \s \D \W \S | 숫자, 낱말 글자, 공백과 그 부정(ASCII) |
* + ? {n} {n,} {n,m} | 되풀이(길게). 뒤에 ? 를 붙이면 짧게 |
a|b | 고르기. 왼쪽이 먼저 |
( … ) (?: … ) | 묶음(갈무리함), 묶음(갈무리 안 함) |
^ $ | 글의 처음과 끝. (?m) 이면 줄마다 |
\b \B | 낱말 경계와 그 부정(ASCII) |
\n \t \r \f \v \0 \xNN | 그 바이트 |
\ 와 그 밖의 글자 | 그 글자(\. \* \\) |
\zs \ze | 맞은 부분의 시작과 끝을 여기로 좁힌다. 패턴 전체는 그대로 맞아야 한다 |
(?i) (?m) (?s) | 패턴의 맨 앞에만. 대소문자 무시(ASCII), 줄마다의 닻, . 이 줄바꿈도 |
역참조, 둘러보기, 유니코드 속성, 이름 붙은 묶음은 없다.
(4) 빈 글에 맞을 수 있는 몸을 되풀이하는 패턴((a*)* 같은 것)은 Perl 이나 Python 과 갈무리의 결과가 다를 수 있다.
16.7.2 낱말
(5) re 는 regex 값이다. “맞은 부분” 은 \zs \ze 로 좁힌 범위다.
| 낱말 | 서명 | 뜻 |
rx | (pat gives regex) | 적재 때 패턴을 프로그램으로 만든다. pat 은 리터럴이나 const |
compile | (pat str gives regex or fail) | 돌 때 만든다. 틀린 패턴은 err.format. 프로그램은 구역에 놓인다 |
is_match | (s str, re gives bool) | 맞는 곳이 있는가 |
match | (s str, re gives str or fail) | 처음 맞은 부분. 없으면 err.not_found |
cut_match | (s str, re gives str, str, str or fail) | 처음 맞은 부분의 앞, 그 부분, 뒤 |
matches | 줄기 | 맞은 부분을 차례로. 블록은 맞은 전체와 묶음마다 str 매개변수를 받는다 |
split | 줄기 | 맞은 부분으로 가른 조각 |
with_match | (s str, re, 블록 or fail) | 처음 맞은 것으로 블록을 한 번 돌린다. 없으면 err.not_found |
replace | (s str, re, rep str gives str) | 모두 바꾼 새 글. rep 의 \0–\9 는 묶음 |
replace_into | (s str, re, rep str, buf gives int or fail) | 바꾼 글을 버퍼에 붙이고 바꾼 횟수를 준다 |
replace_by | (s str, re, buf, 블록 gives int or fail) | 맞은 곳마다 블록이 준 글로 바꿔 버퍼에 붙인다 |
(6) matches split replace 는 다음 자리에서 다시 찾을 때도 글 전체를 본다. 그래서 ^ 와 \b 는 그 자리 앞의 바이트를 안다.
16-rex.pnx
use io
use str
use rex
const pair r"(\w+)=(\d+)"
word yes (b bool gives str) b if "true" else "false" end end
word total (line str gives n int) rem groups arrive as block parameters
0 to n
line pair rex.rx rex.matches do (whole str, key str, value str)
n value str.to_int or_else 0 end add to n
end each
n
end
word demo ()
cat ("sum ", "id=7, x=20; name=kim, n=3" total) io.println
"abc 123" r"\d+" rex.rx rex.is_match yes io.println
"on 2026-10-15 ok" r"(\d+)-(\d+)" rex.rx rex.match or_else (e) "?" end io.println
"key: value" r":\s*" rex.rx rex.cut_match or_else (e) "" "" "" end to (before, m, after)
cat ("[", before, "|", m, "|", after, "]") io.println
"from 2026-10-13 to 2026-11-02" r"(\d{4})-(\d{2})-(\d{2})" rex.rx r"\3/\2/\1" rex.replace io.println
"a , b;c ,d" r"\s*[,;]\s*" rex.rx rex.split do (part str) cat ("<", part, ">") io.print end each
"" io.println
"가나다" r"." rex.rx rex.matches do (ch str) cat ("<", ch, ">") io.print end each rem . is one UTF-8 character
"" io.println
"foobar" r"foo\zsbar" rex.rx rex.match or_else (e) "?" end io.println rem \zs: the match starts here
"Hello" r"(?i)HELLO" rex.rx rex.is_match yes io.println
end
demo
출력
$ pnx 16-rex.pnx
sum 30
true
2026-10
[key|: |value]
from 13/10/2026 to 02/11/2026
<a><b><c><d>
<가><나><다>
bar
true
16-rex-blocks.pnx
use io
use str
use rex
buffer out 64
word demo ()
"2026-10-15" r"(\d+)-(\d+)-(\d+)" rex.rx do (whole str, y str, m str, d str)
cat (d, "/", m, "/", y) io.println
end rex.with_match or_else (e) "no date" io.println end
"none here" r"\d+" rex.rx do (whole str) whole io.println end rex.with_match or_else (e) "no date" io.println end
"the quick brown fox" r"\b[a-z]" rex.rx out do (w str gives str) w str.upper end rex.replace_by or_else (e) -1 end to n
cat (n, " replaced: ", out contents) io.println
end
demo
출력
$ pnx 16-rex-blocks.pnx
15/10/2026
no date
4 replaced: The Quick Brown Fox
16-rex-compile.pnx
use io
use rex
buffer out 64
word swap_with (p str, s str gives n int or fail) rem a pattern made while running
out clear
p rex.compile or_fail to re
s re "-" out rex.replace_into or_fail
end
word demo ()
"X" "aXbXc" swap_with or_else (e) -1 end to n
cat (n, " replaced: ", out contents) io.println
"(" "aXbXc" swap_with or_else (e) cat ("bad pattern: ", e err_text) io.println -1 end drop
end
demo
출력
$ pnx 16-rex-compile.pnx
2 replaced: a-b-c
bad pattern: format
제약
(7) rx 의 패턴은 문자열 리터럴이거나 const 여야 한다. 돌 때 만든 패턴은 compile 을 쓴다.
16-rex-variable.pnx
use rex
word f (s str, p str gives bool) s p rex.rx rex.is_match end
거절
$ pnx 16-rex-variable.pnx
16-rex-variable.pnx:2:38: rx takes a string literal or a const name; a pattern made while running is rex.compile
(8) 모양이 틀린 패턴과 없는 기능(역참조 등)은 거절된다.
16-rex-bad.pnx
use rex
word f (s str gives bool) s r"a{3,1}" rex.rx rex.is_match end
거절
$ pnx 16-rex-bad.pnx
16-rex-bad.pnx:2:39: a counted repetition goes downwards
16-rex-backref.pnx
use rex
word f (s str gives bool) s r"(a)\1" rex.rx rex.is_match end
거절
$ pnx 16-rex-backref.pnx
16-rex-backref.pnx:2:38: back references are not supported
(9) regex 자리에 str 를 주면 거절된다. 블록의 묶음 매개변수는 str 이어야 한다.
16-rex-str.pnx
use rex
word f (s str gives bool) s "a" rex.is_match end
거절
$ pnx 16-rex-str.pnx
16-rex-str.pnx:2:33: this word takes a str and a regex: s re rex.match
16-rex-group-type.pnx
use rex
word f (s str) s r"(a)" rex.rx rex.matches do (m str, g int) end each end
거절
$ pnx 16-rex-group-type.pnx
16-rex-group-type.pnx:2:44: the groups are named str parameters
결함
(10) 찾는 데 드는 작업 공간은 부를 때마다 구역의 끝에서 빌린다. 구역이 모자라면 결함 full 이다. 아래는 구역을 거의 다 채운 뒤에 찾는다.
16-rex-full.pnx
use rex
use str
use io
word found (gives bool)
"x" 4194000 str.repeat to s
s r"(a|b|c|d|e|f|g|h)+x" rex.rx rex.is_match
end
found if "yes" else "no" end io.println
결함으로 멈춤
$ pnx 16-rex-full.pnx
16-rex-full.pnx:6: a declared capacity is used up
s str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
진단
| 조건 | 메시지 |
|---|---|
rx 에 상수가 아닌 패턴 | rx takes a string literal or a const name; a pattern made while running is rex.compile |
| 거꾸로 된 횟수 | a counted repetition goes downwards |
| 역참조 | back references are not supported |
regex 자리의 str | this word takes a str and a regex: s re rex.match |
str 가 아닌 묶음 매개변수 | the groups are named str parameters |
16.8 json — JSON
(1) 모듈 json 은 JSON(RFC 8259) 글을 나무로 만들지 않고 한 방향으로 읽고 쓴다. 읽기는 토큰 하나씩, 쓰기는 버퍼에 바로 한다.
16.8.1 읽기
(2) s json.next 는 s 의 첫 토큰을 읽어 (kind int, value str, rest str) 를 준다(or fail). 끝에 이르면 err.not_found, 토큰이 아닌 것을 만나면 err.format 으로 실패한다. 토큰 사이의 공백, 쉼표, 쌍점은 건너뛴다. 괄호의 짝이나 쉼표의 자리 같은 문법의 짜임은 검사하지 않는다.
(3) kind 는 아래 상수다. 문자열의 value 는 따옴표를 뗀 글이고, 이스케이프가 있으면 풀어서 구역에 쓴 글이다. 수의 value 는 적힌 그대로의 글이다.
| 상수 | 토큰 |
json.object json.object_end | { } |
json.array json.array_end | [ ] |
json.key | 쌍점이 뒤따르는 문자열 |
json.string | 그 밖의 문자열 |
json.number | 수 |
json.boolean json.null | true false, null |
(4) s json.skip 은 값 하나(객체나 배열이면 그 안 전부)를 건너뛰고 나머지를 준다(gives str or fail).
16-json-read.pnx
use io
use json
word kind_name (k int gives str) rem the kinds are constants of json
k json.key eq if "key" return end
k json.string eq if "string" return end
k json.number eq if "number" return end
k json.boolean eq if "boolean" return end
k json.null eq if "null" return end
k json.object eq k json.array eq or if "open" return end
"close"
end
word tokens (s str)
s to rest
loop
rest json.next or_else (e) break end to (kind, value, rest)
cat (kind kind_name, " ", value) io.println
end
end
text END
{"name": "kim", "tags": [1, -2.5e3, true, null], "note": "café"}
END
tokens
출력
$ pnx 16-json-read.pnx
open {
key name
string kim
key tags
open [
number 1
number -2.5e3
boolean true
null null
close ]
key note
string café
close }
16.8.2 쓰기
(5) 쓰는 낱말은 모두 버퍼에 붙이고, 자리가 모자라면 err.full 로 실패한다. 쉼표는 낱말이 알아서 넣는다.
| 낱말 | 서명 |
begin_object end_object begin_array end_array put_null | (buf or fail) |
put_key put_str | (s str, buf or fail). 이스케이프는 낱말이 한다 |
put_int | (n int, buf or fail) |
put_dec | (d dec(N), buf or fail) |
put_bool | (b bool, buf or fail) |
16-json-write.pnx
use io
use json
buffer out 128
word demo (or fail)
out json.begin_object or_fail
"name" out json.put_key or_fail "kim \"k\"" out json.put_str or_fail
"tags" out json.put_key or_fail
out json.begin_array or_fail 1 out json.put_int or_fail true out json.put_bool or_fail out json.put_null or_fail
out json.end_array or_fail
out json.end_object or_fail
end
demo or_else (e) e err_text io.println end
out contents io.println
출력
$ pnx 16-json-write.pnx
{"name":"kim \"k\"","tags":[1,true,null]}
16.8.3 레코드
(6) 줄 buf json.pack 은 레코드의 줄을 필드 이름을 키로 한 객체로 붙인다. s 줄 json.unpack 은 객체를 읽어 이름이 같은 필드에 넣고 객체 뒤의 나머지를 준다. 모르는 키의 값은 건너뛴다. 수는 정수만 받고, 필드의 폭에 맞아야 한다. dec(N) 필드는 점 아래 N 자리까지의 수를 받고, N 자리를 모두 써서 적는다(7.5). null 은 받지 않는다. 모든 것이 맞을 때만 줄에 쓴다 — 실패하면(err.format) 줄은 그대로다.
16-json-record.pnx
use io
use json
record user
name str(8)
id u16
admin bool
end
list users user 4
buffer out 128
word input (gives str)
text END
{"id": 9, "extra": {"x": [1, 2]}, "name": "park", "admin": false} and more
END
end
word demo (or fail)
users add_row or_fail to u
"lee" u to user.name 7 u16 u to user.id true u to user.admin
u out json.pack or_fail
out contents io.println
users add_row or_fail to v
input v json.unpack or_fail to rest rem unknown keys are skipped
cat (v user.name, " ", v user.id, " ", v user.admin, " /", rest, "/") io.println
"{\"id\": 70000}" v json.unpack or_else (e) cat ("70000 in u16: ", e err_text) io.println "" end drop
cat ("the row is unchanged: ", v user.id) io.println
end
demo or_else (e) e err_text io.println end
출력
$ pnx 16-json-record.pnx
{"name":"lee","id":7,"admin":true}
park 9 false / and more/
70000 in u16: format
the row is unchanged: 9
16-dec-text.pnx
use io
use json
use csv
record item
name str(8)
cost dec(2)
end
array one item 1
buffer out 64
word demo (or fail)
0 one idx to r
"pen" r to item.name 1.5 1 rescale 2 rescale r to item.cost
r out json.pack or_fail out contents io.println
out clear r out csv.pack or_fail out contents io.print
"{\"cost\": 3.05}" r json.unpack or_fail drop cat (r item.cost) io.println
"{\"cost\": 3.055}" r json.unpack or_else (e) cat ("3.055: ", e err_text) io.println "" end drop
end
demo or_else (e) e err_text io.println end
출력
$ pnx 16-dec-text.pnx
{"name":"pen","cost":1.50}
pen,1.50
3.05
3.055: format
제약
(7) json.next 와 json.skip 은 str 만, pack 과 unpack 은 레코드의 줄만 받는다.
16-json-type.pnx
use json
word f () 5 json.next drop drop drop end
거절
$ pnx 16-json-type.pnx
16-json-type.pnx:2:13: this word takes the JSON text as a str
진단
| 조건 | 메시지 |
|---|---|
JSON 글이 str 이 아님 | this word takes the JSON text as a str |
16.9 csv — CSV
(1) 모듈 csv 는 RFC 4180 꼴의 CSV 를 읽고 쓴다. 필드는 쉼표로 가르고, 쉼표, 따옴표, 줄바꿈을 담은 필드는 따옴표로 싸며, 안의 따옴표는 두 번 적는다.
(2) s csv.fields 는 줄 하나의 필드를 차례로 내는 줄기(10.6)다. 따옴표로 싼 필드는 따옴표를 떼고 겹친 따옴표를 풀어서 낸다. 닫히지 않은 따옴표는 남은 글 그대로를 마지막 필드로 낸다(줄기는 실패를 알리지 못한다).
(3) 쓰는 낱말은 버퍼에 붙인다(or fail, 자리가 모자라면 err.full). s buf csv.put, n buf,
csv.put_intd buf csv.put_dec, b buf csv.put_bool 은 필드 하나를(앞에 필요한 쉼표와 함께), buf csv.end_line 은 줄 끝을 붙인다. 참거짓은 true false 로 쓴다.
(4) 줄 buf csv.pack 은 레코드의 줄 하나를 한 줄로 붙인다. s 줄 csv.unpack 은 한 줄을 읽어 필드의 차례대로 넣고 그 줄 뒤의 나머지를 준다. 모든 필드가 맞을 때만 줄에 쓴다(err.format). dec(N) 필드는 json 처럼 읽고 쓴다(16.8).
16-csv.pnx
use io
use csv
record user
name str(8)
id u16
admin bool
end
list users user 4
buffer out 128
"a,\"b,c\",\"d\"\"e\",,f" csv.fields do (x str) cat ("[", x, "]") io.print end each
"" io.println
word demo (or fail)
users add_row or_fail to u
"lee, jr" u to user.name 7 u16 u to user.id true u to user.admin
u out csv.pack or_fail rem a line, quoted where needed
"x" out csv.put or_fail 5 out csv.put_int or_fail false out csv.put_bool or_fail out csv.end_line or_fail
out contents io.print
users add_row or_fail to v
"zed,5,true\nrest" v csv.unpack or_fail to rest
cat (v user.name, " ", v user.id, " ", v user.admin, " [", rest, "]") io.println
"zed,5,maybe" v csv.unpack or_else (e) cat ("bad bool: ", e err_text) io.println "" end drop
end
demo or_else (e) e err_text io.println end
출력
$ pnx 16-csv.pnx
[a][b,c][d"e][][f]
"lee, jr",7,true
x,5,false
zed 5 true [rest]
bad bool: format
제약
(5) pack 과 unpack 은 레코드의 줄만 받는다.
16-csv-pack-int.pnx
use csv
buffer b 8
word f () 5 b csv.pack or_else (e) end end
거절
$ pnx 16-csv-pack-int.pnx
16-csv-pack-int.pnx:3:15: pack takes a row of a table of records and a buffer: row buf csv.pack
진단
| 조건 | 메시지 |
|---|---|
| 레코드의 줄이 아닌 값 | pack takes a row of a table of records and a buffer: row buf csv.pack |