7 타입과 값
(1) 모든 값에는 적재 때 정해지는 타입이 있다. 돌 때 값이 타입을 들고 다니지 않고, 돌 때 타입을 묻는 낱말도 없다. 타입이 맞지 않는 프로그램은 적재 때 거절된다.
(2) 타입은 다음과 같다.
| 타입 | 값 |
int | 64 비트 2 의 보수 정수(7.1) |
dec(N) | 소수점 아래 N 자리의 십진 소수, N 은 0 부터 18(7.5) |
u8 u16 u32 u64 i8 i16 i32 | 그 폭의 부호 없는, 부호 있는 정수(7.6). u8 은 바이트, u32 는 코드포인트 |
bool | true, false(7.2) |
str | 바이트의 뷰(7.3). 지역 변수, 인자, 결과에만 쓴다 |
str(N) | N 바이트까지 담는 저장된 글(7.3). 전역, 배열의 칸, 레코드의 필드에 쓴다 |
err | 실패의 오류 번호(11.1) |
| 선언한 타입 | type 이름 바탕 으로 만든, 바탕과 같은 표현의 다른 타입(7.4) |
| 줄 | 레코드를 담은 표(배열, 목록, 풀)의 한 줄. 타입의 이름은 표의 이름이다(8.8) |
| 핸들 | 호스트가 준 객체를 가리키는 번호. 타입의 이름은 호스트가 정한다(19.6) |
| 블록 | word (서명) 으로 적는 낱말 값(10.5) |
regex | 정규 표현식 프로그램(16.7). use rex 가 있는 소스에서만 보인다 |
buffer | 매개변수 타입. 스크립트가 선언한 버퍼(8.7)를 받는다(9.9, 19.3) |
(3) 전역, 배열의 칸, 레코드의 필드는 처음에 0 이다: int 는 0, bool 은 false, str(N) 은 빈 글, 줄은 “없음”(8.8).
7.1 정수
(1) int 의 값은 −263 부터 263−1 까지의 정수다. 모든 처리기에서 같다.
(2) 산술 낱말 add sub mul div mod neg abs 는 결과가 범위를 벗어나면 결함 overflow 로 멈춘다(11.3). 값을 감아 돌리는 낱말(wrap_add …), 끝에서 멈추는 낱말(sat_add …), 넘침을 실패로 알리는 낱말(chk_add …)이 따로 있다(15.1).
(3) div 는 0 쪽으로 버리고, mod 의 결과는 나뉘는 수의 부호를 따른다(a b div b mul a b mod add 가 a 다). floor_div 와 floor_mod 는 아래로 버린다. 0 으로 나누면 결함 div_zero 다.
07-int.pnx
use io
use str
9223372036854775807 str.to_str io.println rem the largest int
7 2 div str.to_str io.println rem division rounds toward zero
-7 2 div str.to_str io.println
-7 2 mod str.to_str io.println rem the remainder has the sign of the dividend
-7 2 floor_div str.to_str io.println rem floor_div rounds down
9223372036854775807 1 wrap_add str.to_str io.println rem wrap_add wraps around
9223372036854775807 1 sat_add str.to_str io.println rem sat_add stops at the edge
출력
$ pnx 07-int.pnx
9223372036854775807
3
-3
-1
-4
-9223372036854775808
9223372036854775807
결함
(4) 넘침은 그 자리에서 실행을 끝낸다. 아래 예에서 넘친 줄 뒤의 코드는 돌지 않는다.
07-int-overflow.pnx
use io
global big int
9223372036854775807 set big
"before" io.println
big 1 add set big
"never printed" io.println
결함으로 멈춤
$ pnx 07-int-overflow.pnx
before
07-int-overflow.pnx:5: integer overflow
7.2 참거짓
(1) bool 의 값은 true 와 false 둘이다. 비교 낱말(eq lt …)이 내고, if while until 과 논리 낱말(and or not)이 받는다.
07-bool.pnx
use io
use str
3 4 lt if "3 is less than 4" else "no" end io.println
true false or not if "never" else "true false or not is false" end io.println
출력
$ pnx 07-bool.pnx
3 is less than 4
true false or not is false
제약
(2) bool 과 int 는 서로 바뀌지 않는다. 조건 자리에 int 를, 산술 낱말에 bool 을 줄 수 없다.
07-bool-not-int.pnx
1 if 2 drop end
거절
$ pnx 07-bool-not-int.pnx
07-bool-not-int.pnx:1:3: a bool is expected here
07-int-not-bool.pnx
true 1 add drop
거절
$ pnx 07-int-not-bool.pnx
07-int-not-bool.pnx:1:8: this word takes two int values, or two of the same declared type
7.3 문자열
(1) 문자열(string)은 바이트의 열이다. UTF-8 이어야 한다는 규칙은 없다 — 바이트를 다루는 낱말은 바이트로, 글자를 다루는 낱말(str.char_count 등)은 UTF-8 로 읽는다.
(2) 문자열의 타입은 둘이다.
str는 뷰다. 어딘가에 있는 바이트(리터럴, 전역의 글, 버퍼, 구역)의 시작과 길이를 가리키며, 만들 때 바이트를 복사하지 않는다. 지역 변수, 인자, 결과, 스택에만 있다.str(N)는 저장된 글(stored text)이다. N 바이트의 자리를 차지하고, 넣을 때 바이트를 복사한다. 전역, 배열과 목록의 칸, 레코드의 필드에만 쓴다. 읽으면 그 자리를 가리키는str뷰가 된다.
07-str.pnx
use io
use str
global name str(8) rem stored text: up to 8 bytes, copied in
word show (s str) cat ("[", s, "] ", s str.len) io.println end
"Pinax" set name
name show
"hello, world" 7 str.skip show rem a view: no byte is copied
출력
$ pnx 07-str.pnx
[Pinax] 5
[world] 5
제약
(3) 전역, 배열, 레코드의 필드에 str 를 쓰면 거절된다. 뷰가 가리키는 바이트가 그 자리보다 먼저 사라질 수 있기 때문이다.
07-str-global.pnx
global name str
거절
$ pnx 07-str-global.pnx
07-str-global.pnx:2:1: stored text is str(N); a str view lives only in locals, arguments and results
결함
(4) str(N) 자리에 N 바이트보다 긴 글을 넣고 그 뒤에 지킴 낱말이 없으면 결함 range 로 멈춘다. 자르지 않는다.
07-str-too-long.pnx
use io
global name str(4)
"four" set name
"more than four" set name
결함으로 멈춤
$ pnx 07-str-too-long.pnx
07-str-too-long.pnx:4: index out of range
(4a) str(N) 자리에 넣는 일(to 로 필드에, set 으로 전역이나 칸에) 뒤에는 지킴 낱말이 올 수 있다(11.2). 그러면 N 바이트보다 긴 글은 실패 range 이고, 자리는 가졌던 것을 그대로 가진다. 짧은 글은 들어가고 지킴 낱말은 아무 일도 하지 않는다. str(N) 목록에 push 하는 일의 지킴 낱말 아래에서도(8.5) 너무 긴 글은 실패 range 다. 목록이 찼든 아니든 그렇다.
07-str-guard.pnx
use io
record entry
name str(4)
end
list rows entry 2
list names str(4) 2
word keep (v str, r rows)
v r to entry.name or_else (e) cat ("refused: ", v, " (", e err_text, ")") io.println return end
cat ("stored: ", r entry.name) io.println
end
word main (or fail)
rows add_row or_fail to r
"four" r keep
"fourteen" r keep
cat ("still: ", r entry.name) io.println
"fourteen" names push or_else (e) cat ("push: ", e err_text) io.println end
end
main or_else (e) e err_text io.println end
출력
$ pnx 07-str-guard.pnx
stored: four
refused: fourteen (range)
still: four
push: range
(4b) fits PLACE 는 넣는 일처럼 str(N) 자리를 부른다: record.field, 전역(MODULE.NAME 도), 또는 그 칸을 위한 배열이나 목록(컬렉션 매개변수도, 9.9). 스택 맨 위의 글을 가져가고 아무것도 주지 않으며 아무것도 쓰지 않는다. N 바이트 이하의 글은 지나가고, 더 긴 글은 실패 range 다. 실패할 수 있는 낱말이므로 뒤에 지킴 낱말이 온다(11.2 (2)). 너무 긴 글이 반쯤 해 놓은 일을 남길 일(줄을 받기 같은)을 하기 전에, 프로그램은 이것으로 자리에 묻는다.
07-fits.pnx
use io
record entry
name str(4)
end
list rows entry 2
array tags str(2) 2
word enter (v str)
v fits entry.name or_else (e) cat ("refused: ", v, " (", e err_text, "), rows ", rows len) io.println return end
rows add_row or_else (e) "full" io.println return end to r
v r to entry.name
cat ("entered: ", r entry.name, ", rows ", rows len) io.println
end
"four" enter
"fourteen" enter
"ab" fits tags or_else (e) "not reached" io.println end
"abc" fits tags or_else (e) cat ("tags: ", e err_text) io.println end
출력
$ pnx 07-fits.pnx
entered: four, rows 1
refused: fourteen (range), rows 1
tags: range
(5) 구역에 만든 뷰는 그 구역이 되감기면 쓸 수 없다. 이 규칙은 12.3 이 정한다.
7.4 선언한 타입
구문
type-decl = "type" NAME base ;
base = "int" | "bool" | "str" | "dec" "(" INT ")" ;(1) type meters int 는 선언한 타입(declared type) meters 를 만든다. 값의 표현은 int 와 같고 실행에 드는 비용도 없지만, 적재 때는 다른 타입이다. 단위가 다른 두 수를 섞는 실수를 막으려고 쓴다.
(2) 바탕 타입의 값 뒤에 타입의 이름을 쓰면 그 타입의 값이 된다(1500 meters). 낱말 raw 는 선언한 타입의 값을 바탕 타입의 값으로 돌린다.
(3) 같은 선언한 타입의 두 값은 add 와 sub 로 더하고 뺄 수 있고, 결과도 그 타입이다. 비교 낱말과 min max 도 같은 타입끼리 쓴다. 다른 산술은 raw 로 바꾼 뒤에 한다.
07-type.pnx
use io
use str
type meters int
type seconds int
word pace (d meters, t seconds gives int) d raw t raw div end rem raw: the plain int
1500 meters 300 meters add 300 seconds pace str.to_str io.println
출력
$ pnx 07-type.pnx
6
(3a) int 위의 선언한 타입 D 의 값에 int 를 곱하거나(D int mul, int D mul) int 로 나누거나 나머지를 구하면(D int div, D int mod) 결과는 D 다. neg 와 abs 도 D 를 준다. 수를 몇 배 하거나 몇으로 나눠도 단위는 그대로이기 때문이다. 두 D 의 곱과 몫은 D 가 아니므로 이 규칙에 들지 않는다.
07-type-scale.pnx
use io
use str
type cents int
word share (total cents, people int gives cents) total people div end
word with_tax (c cents gives cents) c 110 mul 100 div end
1000 cents 3 share with_tax raw str.to_str io.println
출력
$ pnx 07-type-scale.pnx
366
제약
(4) 선언한 타입의 값을 다른 타입의 자리에 주면 거절된다. 아래는 두 인자의 차례를 바꾼 것이다.
07-type-mixed.pnx
type meters int
type seconds int
word pace (d meters, t seconds gives int) d raw t raw div end
300 seconds 1500 meters pace drop rem the two arguments are swapped
거절
$ pnx 07-type-mixed.pnx
07-type-mixed.pnx:4:25: an argument has another type than the word's signature asks for
(5) 선언한 타입의 두 값에 add sub 밖의 산술 낱말을 쓰면 거절된다. 선언한 타입과 int 에 (3a) 밖의 산술 낱말을 써도 거절된다.
07-type-arith.pnx
type meters int
2 meters 3 meters mul drop
거절
$ pnx 07-type-arith.pnx
07-type-arith.pnx:2:19: a declared type is added to and subtracted from its own type, and multiplied, divided or taken modulo by an int; for other arithmetic take raw
7.5 십진 소수
구문
dec-type = "dec" "(" INT ")" ; (* 0 부터 18 *)
DEC = [ sign ] digits "." digits ; (* 지수 없음: 6.4 *)(1) dec(N) 의 값은 소수점 아래 N 자리의 십진수다. 10N 배 한 int 로 담는다: 12.50 은 1250 으로 담긴 dec(2) 다. 어느 처리기에서나 결과가 같다. 범위는 int 의 범위를 10N 으로 나눈 것이다.
(2) 점 앞뒤에 숫자가 있고 지수가 없는 수는 dec(N) 리터럴이다. N 은 점 아래 숫자의 수다: 12.50 은 dec(2), 0.5 는 dec(1), -0.05 는 dec(2) 다.
(3) dec(N) 은 선언한 타입(7.4)처럼 int 위의 수 타입이다: 같은 dec(N) 둘은 더하고 빼고 비교하고 min max 에 준다. dec(N) 에 int 를 곱하고, int 로 나누고(div 처럼 0 쪽으로 자름), 나머지를 구한다. neg abs 는 그대로 둔다. 그에 더해 dec(A) dec(B) mul 은 A+B 가 18 이하일 때 dec(A+B) 를 정확히 준다. sum, minimum, maximum(10.6)은 dec(N) 의 줄기를 받아 dec(N) 을 준다. chk_add chk_sub chk_mul chk_div 는 add sub mul div 처럼 dec(N) 을 받는다. 넘치면 결함 overflow 다.
(4) n dec(N) 은 int n 을 점 아래 0 이 N 개인 수로 만든다(5 dec(2) 는 5.00). d raw 는 담긴 int 를 준다(12.50 raw 는 1250). d M rescale 은 d 를 dec(M) 으로 준다. M 은 0 부터 18 의 상수다. 자리가 늘면 정확하고, 줄면 0 에서 먼 쪽으로 반올림한다(2.345 는 2.35, -2.345 는 -2.35).
07-dec.pnx
use io
use str
type price dec(2)
word demo ()
12.50 to unit rem dec(2): two digits after the point
unit 3 mul to three rem times an int: still dec(2)
three 0.75 add to bill
cat ("bill ", bill, ", a quarter ", bill 4 div) io.println
0.08 to rate
bill rate mul to tax rem dec(2) times dec(2) is dec(4), exact
cat ("tax ", tax, " -> ", tax 2 rescale) io.println
cat (5 dec(2), " ", bill raw, " ", 9.99 price raw) io.println
"19.9" 2 str.to_dec or_else (e) 0.00 end to read
cat (read, " ", read bill gt) io.println
end
demo
출력
$ pnx 07-dec.pnx
bill 38.25, a quarter 9.56
tax 3.0600 -> 3.06
5.00 3825 9.99
19.90 false
07-dec-round.pnx
use io
word show (d dec(3)) cat (d, " -> ", d 2 rescale, ", ", d 0 rescale) io.println end
word demo ()
2.344 show 2.345 show -2.345 show 0.500 show -0.500 show
end
demo
출력
$ pnx 07-dec-round.pnx
2.344 -> 2.34, 2
2.345 -> 2.35, 2
-2.345 -> -2.35, -2
0.500 -> 0.50, 1
-0.500 -> -0.50, -1
(5) type price dec(2) 는 dec(2) 위의 타입을 선언한다. 9.99 price 가 그 값을 만들고 raw 가 dec(2) 를 준다. dec(N) 은 int 처럼 전역, 필드(8 바이트), 배열과 목록의 칸, 큐의 값, 매개변수(9.9)의 타입이 된다. str.to_str 과 cat 은 자리를 모두 써서 적고(16.1), str.to_dec 은 읽는다.
제약
(6) 자리 수가 다른 dec 둘은 더하거나 비교하지 않는다. 하나를 먼저 rescale 한다. dec 를 dec 로 나누지 않는다.
07-dec-scales.pnx
word total (gives dec(2)) 1.50 0.5 add end rem dec(2) and dec(1)
거절
$ pnx 07-dec-scales.pnx
07-dec-scales.pnx:1:36: two dec values of different scales: rescale one first (d 2 rescale)
07-dec-div.pnx
word share (gives dec(2)) 10.00 3.00 div end
거절
$ pnx 07-dec-div.pnx
07-dec-div.pnx:1:38: a dec is divided by an int, not by a dec; for a share, rescale first and divide by the int
7.6 좁은 정수
(1) u8 u16 u32 u64 i8 i16 i32 는 좁은 정수(narrow integer) 타입이다. 범위는 이름이 말한다: u 는 0 부터 2폭−1, i 는 −2폭−1 부터 2폭−1−1. u8 은 바이트의 타입이고 u32 는 코드포인트의 타입이다. 64 비트 부호 있는 정수는 int 다. i64 라는 타입은 없다.
(2) 좁은 정수는 int 와 다른 타입이고 서로도 다른 타입이다. 변환은 낱말로 적는다. n u8 은 int 나 다른 좁은 정수 n 을 u8 로 만들고, 값이 범위 밖이면 결함 overflow 다. 자르지 않는다. 리터럴도 같은 꼴이다: 255 u8. 상수가 범위 밖이면 적재 때 거절된다. x raw 는 담긴 int 를 준다. u64 의 raw 는 64 비트를 그대로 int 에 담는다(263 이상은 음수). n low_u8 … n low_i32 는 아래 비트만 받는다. 이것이 자르는 낱말이다.
(3) add sub mul div mod 는 같은 좁은 정수 둘을 받아 같은 타입을 준다. 결과가 범위를 벗어나면 결함 overflow 다. wrap_add wrap_sub wrap_mul 은 2폭 을 법으로 감고, sat_add sat_sub sat_mul 은 범위의 끝에서 멈추고, chk_add chk_sub chk_mul chk_div 는 실패로 알린다. 비교 낱말은 같은 타입 둘을 받는다. u64 는 부호 없이 비교하고 나눈다. neg abs min max clamp 는 받지 않는다(15.1).
(4) 비트 낱말은 폭 안에서 한다. bit_not 은 폭의 비트만 뒤집고, shl 은 폭을 넘는 비트를 버리고, shr 은 부호 있는 타입이면 부호를 지키고 없는 타입이면 0 을 채운다. u_shr u_div u_mod u_lt u_le u_gt u_ge floor_div floor_mod 는 부호 없는 타입에서 shr div mod lt le gt ge div mod 와 같고, 부호 있는 타입은 받지 않는다. count_ones leading_zeros trailing_zeros 는 폭의 비트를 세어 int 를 주고(0 이면 폭), byte_swap 은 폭의 바이트를 뒤집는다. rotl rotr mul_hi u_mul_hi 는 u64 만 받는다.
(5) 좁은 정수는 int 처럼 전역, 배열과 목록의 칸, 큐의 값, 매개변수와 결과, 호스트 낱말의 서명(19.2)의 타입이 되고, type opcode u8 처럼 선언한 타입(7.4)의 바탕이 된다. 배열, 목록, 큐의 칸과 레코드의 필드는 그 폭만큼의 바이트다(array a u8 1024 는 1024 바이트); 전역 하나는 8 바이트 자리다. 문자 리터럴은 u8 이나 u32 다(6.5). case 는 좁은 정수를 견준다(10.3). 호스트 낱말은 값을 64 비트 정수 하나로 받고 주며, 결과가 범위 밖이면 결함 overflow 다(19.2).
07-narrow.pnx
use io
use str
word show (n int) n str.to_str io.println end
word demo ()
250 u8 5 u8 add raw show rem 255
0 u8 1 u8 wrap_sub raw show rem 255: wraps at 2^8
200 u8 100 u8 sat_add raw show rem 255: stops at the end
200 u8 bit_not raw show rem 55: eight bits flipped
-1 low_u8 raw show rem 255: the low byte of -1
0 u64 bit_not 1 u64 gt if "u64 compares unsigned" io.println end
0 u64 bit_not raw show rem -1: the bits as an int
end
demo
출력
$ pnx 07-narrow.pnx
255
255
255
55
255
u64 compares unsigned
-1
제약
(6) int 와 좁은 정수, 또는 다른 좁은 정수 둘을 한 낱말에 주면 거절된다. 범위 밖의 상수를 변환하면 거절된다.
07-narrow-mix.pnx
word f (gives u8) 1 u8 1 add end
거절
$ pnx 07-narrow-mix.pnx
07-narrow-mix.pnx:1:26: this word takes two values of one type: u8 with u8, int with int; u8 and raw convert
07-narrow-range.pnx
word f (gives u8) 256 u8 end
거절
$ pnx 07-narrow-range.pnx
07-narrow-range.pnx:1:23: the constant is outside the range of this type; low_u8 and the others keep the low bits
결함
(7) 범위를 벗어나는 결과와 변환은 결함 overflow 로 멈춘다.
07-narrow-overflow.pnx
use io
word byte (n int gives u8) n u8 end
"before" io.println
255 byte 1 u8 add drop
"never printed" io.println
결함으로 멈춤
$ pnx 07-narrow-overflow.pnx
before
07-narrow-overflow.pnx:4: integer overflow
진단
| 조건 | 메시지 |
|---|---|
조건 자리에 bool 이 아닌 값 | a bool is expected here |
좁은 정수와 int, 또는 다른 좁은 정수 둘 | this word takes two values of one type: u8 with u8, int with int; u8 and raw convert |
| 범위 밖의 상수를 변환 | the constant is outside the range of this type; low_u8 and the others keep the low bits |
| 선언한 타입의 값을 바로 변환 | u8, u16, u32, u64, i8, i16, i32 and low_u8 ... are made from an int or from one another; for a declared type take raw first |
부호 있는 좁은 정수에 u_shr | u_shr takes an int or an unsigned type; a signed type shifts with shr |
부호 있는 좁은 정수에 u_div u_mod floor_div floor_mod | u_div, u_mod, floor_div and floor_mod take an int or an unsigned type |
부호 있는 좁은 정수에 u_lt u_le u_gt u_ge | u_lt, u_le, u_gt and u_ge take an int or an unsigned type; a signed type compares with lt |
u64 아닌 좁은 정수에 rotl rotr | rotl and rotr take an int or a u64; for a narrow type take raw |
u64 아닌 좁은 정수에 mul_hi u_mul_hi | mul_hi and u_mul_hi take an int or a u64; for a narrow type take raw |
좁은 정수에 abs | abs takes an int or a declared type over int; a narrow type has no abs |
산술 낱말의 두 값의 타입이 다르거나 int 가 아님 | this word takes two int values, or two of the same declared type |
| 선언한 타입에 (3)과 (3a) 밖의 산술 | a declared type is added to and subtracted from its own type, and multiplied, divided or taken modulo by an int; for other arithmetic take raw |
| 인자의 타입이 서명과 다름 | an argument has another type than the word's signature asks for |
전역 등에 str | stored text is str(N); a str view lives only in locals, arguments and results |
레코드의 필드에 str | a field holds str(N); a str view lives only in locals, arguments and results |
자리 수가 다른 dec 둘 | two dec values of different scales: rescale one first (d 2 rescale) |
dec 를 dec 로 나눔 | a dec is divided by an int, not by a dec; for a share, rescale first and divide by the int |
dec(A) dec(B) mul 이 18 자리를 넘음 | dec(A) times dec(B) is dec(A+B), and A+B is at most 18; rescale one first |
| 0 부터 18 밖의 자리 수 | dec(N) has 0 to 18 digits after the point |
str(N) 이 아닌 자리 앞의 fits | fits names a place of type str(N): record.field, a global, or an array or a list |
지역 변수 앞의 fits | fits names a place a store keeps; a local holds a text of any length |
선언되지 않은 이름 앞의 fits | fits names a declared global, array or list; there is none of this name |
str 이 아닌 값의 fits | fits takes a text: text fits place |
뒤에 자리가 없는 fits | fits is followed by a place: text fits record.field, or the name of a global |