16 표준 모듈

(1) 표준 모듈은 처리기에 들어 있다. use 모듈 로 열고 모듈.낱말 로 쓴다(13.1). 표준 모듈의 낱말은 호스트의 허락 없이 바깥과 닿지 못한다 — 파일도, 시계도, 환경도 쓰지 않는다.

(2) 표준 모듈의 낱말은 새 저장소를 만들지 않는다. 결과는 다음 가운데 하나다.

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)를 내는 낱말:

(8) 버퍼에 쓰는 낱말:

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_decodeRFC 4648 의 Base64. 풀 때 끝의 = 는 없어도 된다
hex_encode hex_decode바이트마다 십육진 두 자리. 쓸 때 소문자, 읽을 때 대소문자 모두
url_encode url_decodeRFC 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_u64
put_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_varintLEB128. 값을 부호 없는 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 자리의 strthis 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.nulltrue 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_int
, d 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