7 타입과 값

(1) 모든 값에는 적재 때 정해지는 타입이 있다. 돌 때 값이 타입을 들고 다니지 않고, 돌 때 타입을 묻는 낱말도 없다. 타입이 맞지 않는 프로그램은 적재 때 거절된다.

(2) 타입은 다음과 같다.

타입값
int64 비트 2 의 보수 정수(7.1)
dec(N)소수점 아래 N 자리의 십진 소수, N 은 0 부터 18(7.5)
u8 u16 u32 u64 i8 i16 i32그 폭의 부호 없는, 부호 있는 정수(7.6). u8 은 바이트, u32 는 코드포인트
booltrue, 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) 문자열의 타입은 둘이다.

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_shru_shr takes an int or an unsigned type; a signed type shifts with shr
부호 있는 좁은 정수에 u_div u_mod floor_div floor_modu_div, u_mod, floor_div and floor_mod take an int or an unsigned type
부호 있는 좁은 정수에 u_lt u_le u_gt u_geu_lt, u_le, u_gt and u_ge take an int or an unsigned type; a signed type compares with lt
u64 아닌 좁은 정수에 rotl rotrrotl and rotr take an int or a u64; for a narrow type take raw
u64 아닌 좁은 정수에 mul_hi u_mul_himul_hi and u_mul_hi take an int or a u64; for a narrow type take raw
좁은 정수에 absabs 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
전역 등에 strstored text is str(N); a str view lives only in locals, arguments and results
레코드의 필드에 stra 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) 이 아닌 자리 앞의 fitsfits names a place of type str(N): record.field, a global, or an array or a list
지역 변수 앞의 fitsfits names a place a store keeps; a local holds a text of any length
선언되지 않은 이름 앞의 fitsfits names a declared global, array or list; there is none of this name
str 이 아닌 값의 fitsfits takes a text: text fits place
뒤에 자리가 없는 fitsfits is followed by a place: text fits record.field, or the name of a global