8 선언
8.1 일반
구문
declaration = [ "export" ] ( [ "bound" ] bindable | "shared" shareable | decl ) ;
bindable = "global" NAME type
| "array" NAME type capacity
| "buffer" NAME capacity ;
shareable = bindable
| "list" NAME type capacity
| "index" NAME NAME PATH ;
decl = "const" NAME literal
| "err" NAME
| "list" NAME type capacity
| "queue" NAME type capacity
| "pool" NAME NAME capacity
| "record" NAME { field | "big_endian" } "end"
| "type" NAME base
| "index" NAME NAME PATH
| "tasks" NAME capacity [ size [ size [ size ] ] ] [ "apart" ]
| "extern" c-name "(" effect ")" "from" STRING [ "as" NAME ]
| "test" NAME { body } "end" ;
capacity = size | "host" ;
size = INT | NAME ; (* NAME: a const *)(1) 선언(declaration)은 이름과 그 이름이 가리키는 것(값, 저장소, 타입)을 만든다. 선언은 정의 밖, 소스의 맨 바깥에만 쓴다.
(2) 처리기는 소스를 위에서 아래로 한 번 읽는다. 이름은 쓰는 자리보다 앞에서 선언돼 있어야 한다. 예외는 자기를 부르는 낱말과 forward(8.11)다.
(3) 한 모듈 안에서 같은 이름을 두 번 짓지 못한다. 종류가 달라도 그렇다. 핵 낱말과 타입의 이름도 다시 짓지 못한다.
(4) 용량(size)은 양의 정수 리터럴이거나 그런 값의 const 다. 용량은 적재 때 정해지고 돌 때 바뀌지 않는다. 모든 저장소는 적재 때 상태의 덩이 안에 자리를 잡는다.
(4a) 배열, 버퍼, 목록, 큐, 풀, 작업 묶음의 용량은 host 로 쓸 수 있다. 그러면 그것은 호스트가 적재 전에 그 선언에 준 수다(19.4 (1d)). 적재는 그 수를 거기에 적은 것처럼 다룬다. 호스트가 수를 주지 않은 선언에서 host 를 만난 적재는 거절되고, 메시지가 그 선언의 이름을 댄다. host 는 그 자리에서만 선언의 낱말이다. 그 자리에서는 같은 이름의 상수를 찾지 않는다.
08-host-capacity.pnx
tasks handlers host 32 8192 apart rem as many as the host serves at once
list seen int host
word count (gives n int) seen len end
거절
$ pnx 08-host-capacity.pnx
08-host-capacity.pnx:1:16: the host gave no capacity for handlers (pinax_capacity)
(5) 전역, 배열의 칸, 목록·큐·풀의 칸, 버퍼, 레코드의 필드는 0 으로 시작한다(7 (3)).
제약
(6) 이미 있는 이름을 다시 지으면 거절된다.
08-name-twice.pnx
global total int
const total 5
거절
$ pnx 08-name-twice.pnx
08-name-twice.pnx:2:7: this name is already defined
(7) 선언보다 앞에서 이름을 쓰면 거절된다.
08-use-before.pnx
use str
word show (gives s str) total str.to_str end
global total int
거절
$ pnx 08-use-before.pnx
08-use-before.pnx:2:25: this name is not declared before this place
8.2 상수
(1) const 이름 리터럴 은 상수(constant)를 만든다. 타입은 리터럴의 타입이다. 상수는 저장소가 없다 — 쓰는 자리에 값이 들어간다. 용량, case 의 가지 값, row 의 값에 쓸 수 있다.
제약
(2) 상수에는 넣지 못한다. 정의 안에서 v to 상수 는 같은 이름의 지역 변수를 새로 만들려는 것이 되어 가림 금지(9.5)로 거절된다. 정의 밖에서는 to 가 지역을 만들지 않으므로 거절된다. set 은 전역에만 쓴다(9.4).
08-const-store.pnx
const limit 3
word raise () 4 to limit end
거절
$ pnx 08-const-store.pnx
08-const-store.pnx:2:20: this name is already visible here; a new name may not hide it
8.3 전역
(1) global 이름 타입 은 값 하나를 담는 전역(global)을 만든다. 타입은 int bool err dec(N) str(N), 선언한 타입, 줄의 타입(표의 이름), 핸들 타입이다. str 는 쓰지 못한다(7.3).
(2) 전역을 읽으려면 이름을 쓰고, 넣으려면 v set 이름 을 쓴다(9.4). 상태가 살아 있는 동안 값이 남는다 — 한 낱말의 실행이 끝나도, 다음 적재가 와도 그대로다.
08-const-global.pnx
use io
use str
const limit 3
const greeting "hello"
global hits int
global last str(16)
global seen bool
hits str.to_str io.println rem a global starts at zero
limit set hits
greeting set last
true set seen
cat (last, " ", hits, " ", seen) io.println
출력
$ pnx 08-const-global.pnx
0
hello 3 true
8.4 배열
구문
assign-item = "assign" NAME "(" value { "," value } ")" ;(1) array 이름 타입 용량 은 칸이 용량만큼 있는 배열(array)을 만든다. 배열은 늘 가득 차 있다: len 은 용량과 같다. 원소 타입은 int bool err dec(N) str(N), 좁은 정수(7.6), 선언한 타입, 레코드의 이름, 또는 표의 이름이다. 표의 이름이면 칸은 그 표의 줄을 담는다(8.8 (5a)). 좁은 정수의 칸은 그 폭만큼의 바이트이고, 다른 값의 칸은 8 바이트다. 목록과 큐의 칸도 같다.
(2) i 배열 idx 는 칸 i 의 값을 읽는다. v i set 배열 은 칸 i 에 넣는다. 모음 매개변수(9.9)의 칸도 같은 꼴로 넣는다. 번호는 0 부터 센다. 레코드의 배열에서 idx 는 값이 아니라 줄(8.8)을 낸다.
(3) assign 배열 (값, …) 은 정의 밖에서 배열을 0 번 칸부터 채운다. 값이 칸보다 적으면 나머지 칸은 그대로다. 같은 배열에 다시 쓰면 0 번 칸부터 다시 넣는다. 자료 적재(13.4)에서도 쓸 수 있다.
(4) 배열의 이름을 줄기로 쓰면 칸의 값을 0 번부터 차례로 낸다(10.6).
08-array.pnx
use io
use str
array primes int 6
assign primes (2, 3, 5, 7) rem cells 4 and 5 stay 0
11 4 set primes rem v i to array: store into cell i
primes do (p int) p str.to_str io.print " " io.print end each
"" io.println
cat ("len ", primes len, ", cell 2 is ", 2 primes idx) io.println
출력
$ pnx 08-array.pnx
2 3 5 7 11 0
len 6, cell 2 is 5
제약
(5) assign 의 값이 칸보다 많으면 거절된다.
08-assign-many.pnx
array small int 2
assign small (1, 2, 3)
거절
$ pnx 08-assign-many.pnx
08-assign-many.pnx:2:21: assign has more values than the array has cells
결함
(6) 번호가 0 보다 작거나 용량과 같거나 크면 결함 range 다.
08-array-range.pnx
use io
array primes int 4
"reading cell 4 of 4" io.println
4 primes idx drop
결함으로 멈춤
$ pnx 08-array-range.pnx
reading cell 4 of 4
08-array-range.pnx:4: index out of range
8.5 목록
(1) list 이름 타입 용량 은 길이가 0 에서 용량까지 자라는 목록(list)을 만든다. 칸을 읽고 쓰는 것은 배열과 같으나, 번호는 길이 안에서만 쓴다.
(2) v 목록 push 는 끝에 하나를 더하고, 목록 pop 은 끝의 하나를 빼서 준다. 둘은 실패할 수 있다 (or fail): 가득 찬 목록에 push 하면 err.full, 빈 목록에서 pop 하면 err.range 다. str(N) 목록에 너무 긴 글을 push 하면 err.range 다(7.3 (4a)). 레코드의 목록에는 목록 add_row 로 빈 줄을 더하고 그 줄을 받는다. 목록 clear 는 길이를 0 으로 한다.
08-list.pnx
use io
use str
list stack int 3
word fill (or fail)
1 stack push or_fail
2 stack push or_fail
end
fill or_else (e) "fill failed" io.println end
cat ("len ", stack len, " of ", stack capacity) io.println
stack pop or_else (e) -1 end str.to_str io.println
cat ("len ", stack len) io.println
출력
$ pnx 08-list.pnx
len 2 of 3
2
len 1
08-list-full.pnx
use io
list two int 2
1 two push or_else (e) end
2 two push or_else (e) end
3 two push or_else (e) cat ("third push failed: ", e err_text) io.println end
출력
$ pnx 08-list-full.pnx
third push failed: full
(2a) n 목록 truncate 는 길이를 n 으로 줄인다. 정수, 글, 레코드의 목록 모두에 쓴다. add_row 로 더한 줄의 값이 틀린 것을 알았을 때 그 줄을 되돌리는 데 쓴다.
08-list-truncate.pnx
use io
record city
name str(16)
people int
end
list cities city 4
rem Add a row, and take it back when its values turn out wrong.
word add_city (name str, people int or fail)
cities len to before
cities add_row or_fail to c
name c to city.name
people 0 lt if before cities truncate err.format fail end
people c to city.people
end
"Seoul" 9411000 add_city or_else (e) end
"Nowhere" -1 add_city or_else (e) cat ("refused: ", e err_text) io.println end
cat (cities len, " rows") io.println
출력
$ pnx 08-list-truncate.pnx
refused: format
1 rows
제약
(2b) 줄이 다른 곳에 남는 레코드의 목록은 truncate 하지 못한다. 그 목록의 줄을 담는 필드나 전역이나 모음, 그 목록에 row 로 이름 붙인 줄, 그 목록의 찾아보기(8.10)가 같은 소스 안에 하나라도 있으면 거절된다. 잘라 낸 자리에 새 줄이 들어서면 그것들이 엉뚱한 줄을 가리키기 때문이다. 지역에 든 줄은 쓸 때 검사한다(결함 range).
08-list-truncate-kept.pnx
record city
name str(16)
end
list cities city 4
index by_name cities city.name
word forget () 0 cities truncate end
거절
$ pnx 08-list-truncate-kept.pnx
08-list-truncate-kept.pnx:6:25: this list's rows are kept by a field, a global, a collection, a named row or an index; it cannot be truncated
결함
(3) 길이 밖의 번호는 용량 안이라도 결함 range 다. truncate 에 길이보다 큰 수나 음수를 주어도 결함 range 다.
08-list-past-len.pnx
list items int 8
1 items push or_else (e) end
1 items idx drop rem the list holds one value: index 1 is past it
결함으로 멈춤
$ pnx 08-list-past-len.pnx
08-list-past-len.pnx:3: index out of range
진단
| 조건 | 메시지 |
|---|---|
줄이 남는 목록을 truncate | this list's rows are kept by a field, a global, a collection, a named row or an index; it cannot be truncated |
8.6 큐
(1) queue 이름 타입 용량 은 먼저 넣은 것이 먼저 나오는 큐(queue)를 만든다. 원소 타입은 int bool err dec(N) 이다. v 큐 put 은 뒤에 넣고, 큐 take 는 앞에서 빼서 주며, 큐 peek 은 빼지 않고 앞의 것을 준다. 셋 다 실패할 수 있다: 가득 찬 큐에 넣으면 err.full, 빈 큐에서 꺼내면 err.range 다.
08-queue.pnx
use io
use str
queue jobs int 4
word show (gives s str or fail) jobs take or_fail str.to_str end
10 jobs put or_else (e) end
20 jobs put or_else (e) end
jobs peek or_else (e) 0 end str.to_str io.println
show or_else (e) "?" end io.println
show or_else (e) "?" end io.println
show or_else (e) cat ("empty: ", e err_text) end io.println
출력
$ pnx 08-queue.pnx
10
10
20
empty: range
8.7 버퍼
(1) buffer 이름 용량 은 용량만큼의 바이트를 담는 버퍼(buffer)를 만든다. 버퍼는 고칠 수 있는 바이트의 열이다. 글을 조금씩 이어 붙일 때 쓴다.
(2) s 버퍼 append, n 버퍼 append_int(십진; n 은 int 나 좁은 정수), b 버퍼 append_byte(b 는 u8) 는 끝에 붙이고, 자리가 모자라면 err.full 로 실패한다(실패하면 아무것도 붙이지 않는다). 버퍼 contents 는 지금 내용의 str 뷰를 준다. n 버퍼 truncate 는 길이를 n 으로 줄인다. 버퍼 clear 는 길이를 0 으로 한다.
08-buffer.pnx
use io
buffer out 32
word build (or fail)
"x=" out append or_fail
42 out append_int or_fail
'!' out append_byte or_fail
end
build or_else (e) "full" io.println end
out contents io.println
2 out truncate
out contents io.println
출력
$ pnx 08-buffer.pnx
x=42!
x=
(3) contents 가 준 뷰는 버퍼의 바이트를 가리킨다. 버퍼를 고치면 뷰가 보는 바이트도 바뀐다.
8.8 레코드와 줄
구문
record = "record" NAME { field | "big_endian" } "end" ;
field = NAME width ;
width = "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64"
| "int" | "bool" | "varint" | "dec" "(" INT ")" | "str" "(" size ")"
| NAME ; (* NAME: a table *)
row-item = [ "export" ] "row" NAME [ NAME ] "(" value { "," value } ")" ;
value = literal | NAME ; (* NAME: a const, a row name, none *)(1) record 이름 … end 는 필드의 나열인 레코드(record) 타입을 만든다. 필드마다 폭을 적는다. int 와 i64 필드는 읽으면 int, u8 … i32 필드는 그 좁은 정수(7.6), bool 필드는 bool, dec(N) 필드는 dec(N)(8 바이트), str(N) 필드는 str 뷰가 된다. 필드에는 그 타입의 값만 넣는다. 필드의 타입에 표의 이름을 쓰면 그 표의 줄을 담는다.
(2) 레코드는 값으로 다루지 않는다. 레코드를 원소로 하는 배열·목록·풀(표(table))을 선언하고, 그 한 칸인 줄(row)을 다룬다. 줄의 타입은 표의 이름이다. 줄은 표의 번호 하나를 담고, 쓸 때마다 범위를 검사한다(풀이면 세대도, 8.9).
(3) 필드는 경로 레코드.필드 로 적는다. 줄 레코드.필드 는 읽고, v 줄 to 레코드.필드 는 넣는다.
(4) row 표 (값, …) 는 정의 밖에서 목록에 줄 하나를 더한다. 값은 필드의 차례로 적고, 뒤쪽 필드는 줄일 수 있다(0 이 된다). row 표 이름 (…) 은 그 줄에 이름을 붙인다. 줄의 이름은 뒤의 row 값과 코드에서 그 줄로 쓴다.
08-record.pnx
use io
record user
name str(16)
age u8
admin bool
end
list users user 8
row users ("kim", 30, true)
row users lee ("lee", 25) rem a named row; the fields left out stay zero
word show (u users) cat (u user.name, " ", u user.age, " ", u user.admin) io.println end
users do (u users) u show end each
26 u8 lee to user.age rem v row to record.field
lee show
출력
$ pnx 08-record.pnx
kim 30 true
lee 25 false
lee 26 false
(5) 줄을 담는 필드와 전역의 처음 값은 “없음” 이다. 줄 is_none 은 없음인지 묻고, 표 none 은 그 표의 없음 줄을 낸다. 줄 row_index 는 줄의 번호를 준다.
(5a) 원소 타입이 표의 이름인 배열이나 목록은 그 표의 줄을 8 바이트 칸에 담는다. 칸에는 줄의 번호에 1 을 더한 값이 들고, 풀의 줄이면 그 세대가 위쪽 32 비트에 든다. 0 은 없음이고, 칸은 모두 없음으로 시작한다. 칸은 다른 칸처럼 읽고, 넣고, push·pop 하고, 돌며, 들어가는 값은 그 표의 줄이어야 한다. 칸에서 읽은 줄은 다른 줄처럼 쓰는 자리에서 검사한다. 큐는 줄을 담지 않는다. alloc 이 막 준 줄(8.9)은 set 으로 칸에 넣는다. set 은 필드처럼 그 줄을 넘겨받는다. push 는 그 줄을 거부한다. 실패한 push 는 그 줄을 잃기 때문이다.
08-row-cells.pnx
use io
record city
name str(12)
people int
end
list cities city 4
list big cities 4 rem rows of cities
row cities ("Oslo", 709)
row cities ("Bergen", 291)
row cities ("Tromso", 78)
cities do (c cities gives bool) c city.people 100 gt end big filter_into or_else (e) end
big cities sort_by city.name
big do (c cities) c city.name io.println end each
출력
$ pnx 08-row-cells.pnx
Bergen
Oslo
(6) big_endian 은 16.5 의 bytes.pack 과 bytes.unpack 이 쓰는 바이트 순서만 정한다. 메모리 안의 모양은 바꾸지 않는다.
제약
(7) row 의 값은 필드의 차례와 타입에 맞아야 한다.
08-row-types.pnx
record user
name str(16)
age u8
end
list users user 8
row users (30, "kim")
거절
$ pnx 08-row-types.pnx
08-row-types.pnx:6:12: a value has another type than its field
결함
(8) 좁은 정수 필드에 넣을 int 는 먼저 그 타입으로 바꾼다(n u8); 값이 폭에 맞지 않으면 그 변환이 결함 overflow 다(7.6). 자르지 않는다 — 잘라 넣으려면 low_u8 처럼 아래 비트를 받는 낱말을 쓴다. 없음 줄의 필드를 읽거나 쓰면 결함 range 다.
08-record-narrow.pnx
record pixel
level u8
end
array image pixel 4
word set_level (n int) n u8 0 image idx to pixel.level end rem the conversion checks the range
300 set_level rem 300 does not fit in u8
결함으로 멈춤
$ pnx 08-record-narrow.pnx
08-record-narrow.pnx:5: integer overflow
n int = 300
8.9 풀
(1) pool 이름 레코드 용량 은 줄을 얻고 돌려주는 풀(pool)을 만든다. 풀 alloc 은 빈 줄 하나를 얻고(or fail, 남은 줄이 없으면 err.full), 줄 풀 free 는 돌려준다. 줄 풀 is_alive 는 그 줄이 아직 살아 있는지 묻는다.
(2) 풀의 줄은 번호와 함께 세대(generation)를 담는다. 줄을 돌려주면 그 칸의 세대가 바뀐다. 그래서 돌려준 줄을 다시 쓰면 — 그 사이 같은 칸을 다른 줄이 얻었더라도 — 알아챈다.
(3) alloc 이 준 줄은 owned 값(12.4)이다. 모든 경로에서 정확히 한 번, 필드나 전역에 넣거나 free 에 줘야 한다. 넣은 뒤에는 세대 검사가 그 줄을 지킨다.
08-pool.pnx
use io
record node
value int
end
pool nodes node 2
global kept nodes
word demo (or fail)
nodes alloc or_fail to a rem an owned row: it must be stored or freed
7 a to node.value
a set kept rem stored: the global holds the row now
cat ("value ", kept node.value, ", alive: ", kept nodes is_alive) io.println
kept nodes free
cat ("alive after free: ", kept nodes is_alive) io.println
end
demo or_else (e) "no room" io.println end
출력
$ pnx 08-pool.pnx
value 7, alive: true
alive after free: false
(3a) 풀 do (r 풀) … end each 는 풀에서 살아 있는 줄을 칸의 차례로 들른다. 얻은 차례가 아니다. 걷기가 닿기 전에 블록 안에서 돌려준 줄은 들르지 않는다. 블록 안에서 얻은 줄은 그 칸이 뒤에 있으면 들른다. 풀을 걷는 조합 낱말(10.6)은 each 하나뿐이다.
제약
(4) 얻은 줄을 넣지도 돌려주지도 않는 경로가 있으면 거절된다.
08-pool-leak.pnx
record node
value int
end
pool nodes node 2
word leak (or fail)
nodes alloc or_fail to a
7 a to node.value
end
거절
$ pnx 08-pool-leak.pnx
08-pool-leak.pnx:8:1: an owned value in a local is not given on every path (close, free or store it)
(4a) 풀에 each 가 아닌 조합 낱말을 쓰면 거절된다.
08-pool-walk.pnx
record node
value int
end
pool nodes node 4
word live (gives n int)
nodes do (r nodes) true end count
end
거절
$ pnx 08-pool-walk.pnx
08-pool-walk.pnx:6:22: a pool is walked by each only
결함
(5) 돌려준 줄을 쓰면 결함 stale 이다. pnx 는 결함 아래에 그 낱말의 지역 변수를 함께 보인다.
08-pool-stale.pnx
record node
value int
end
pool nodes node 2
global keep nodes
word demo (or fail)
nodes alloc or_fail to a
a set keep rem stored: the row is handed over
keep nodes free
keep node.value drop rem the row was given back: stale
end
demo or_else (e) end
결함으로 멈춤
$ pnx 08-pool-stale.pnx
08-pool-stale.pnx:10: a row or handle was used after it was given back
a nodes = 4294967297
진단
| 조건 | 메시지 |
|---|---|
풀에 each 가 아닌 조합 낱말 | a pool is walked by each only |
8.10 찾아보기
(1) index 이름 표 레코드.필드 는 레코드의 표(배열, 목록, 풀)를 한 필드로 찾는 찾아보기(index)를 만든다. 키 필드는 int 이거나 str(N) 이다. 찾아보기는 표의 용량에 맞춘 고정 크기의 해시 표이고 줄의 번호만 담는다. 자료는 표에 있다.
(2) 키 찾아보기 lookup 은 그 키의 줄을 준다(or fail, 없으면 err.not_found). 줄 찾아보기 insert 는 줄을 넣고(or fail, 같은 키가 이미 있으면 실패), 줄 찾아보기 remove 는 뺀다. 키 필드를 바꾸려면 빼고, 고치고, 다시 넣는다.
(2a) 키 찾아보기 has 는 그 키의 줄이 찾아보기에 있으면 true, 없으면 false 를 준다. 실패하지 않는다.
(3) 적재 때 row 로 적은 줄은 찾아보기에 들어 있다. 돌 때 add_row 로 더한 줄은 insert 해야 찾아진다.
08-index.pnx
use io
use str
record user
name str(16)
age u8
end
list users user 8
index by_name users user.name
row users ("kim", 30)
row users ("lee", 25)
word find_age (name str gives age int or fail) name by_name lookup or_fail user.age raw end
"lee" find_age or_else (e) -1 end str.to_str io.println
"park" find_age or_else (e) -1 end str.to_str io.println
출력
$ pnx 08-index.pnx
25
-1
08-index-insert.pnx
use io
use str
record user
name str(16)
age int
end
list users user 4
index by_name users user.name
word add_user (name str, age int, indexed bool or fail)
users add_row or_fail to r
name r to user.name
age r to user.age
indexed if r by_name insert or_fail end rem a row added while running is indexed by insert
end
word age_of (name str gives int or fail) name by_name lookup or_fail user.age end
"kim" 30 true add_user or_else (e) end
"lee" 25 false add_user or_else (e) end
"kim" age_of or_else (e) -1 end str.to_str io.println
"lee" age_of or_else (e) -1 end str.to_str io.println rem not inserted: not found
출력
$ pnx 08-index-insert.pnx
30
-1
(3a) 찾아보기의 표는 풀(8.9)일 수 있다. 그러면 lookup 은 줄을 지금 모습대로, 세대와 함께 준다. 돌려준 뒤의 줄을 insert 나 remove 에 주면 결함 stale 이다. free 는 먼저 그 풀 위의 모든 찾아보기에서 줄을 뺀다. alloc 이 방금 준 줄은 insert 에 넘겨준다(12.4). insert 가 실패하면 실패를 넘기기 전에 줄을 풀에 돌려준다.
08-index-pool.pnx
use io
record user
name str(16)
age int
end
pool users user 4
index by_name users user.name
word add_user (name str, age int or fail)
users alloc or_fail to r
name r to user.name
age r to user.age
r by_name insert or_fail rem hands the row over to the index
end
word forget (name str)
name by_name lookup or_else (e) return end to r
r users free rem out of by_name too
end
"kim" 30 add_user or_else (e) end
"lee" 25 add_user or_else (e) end
"kim" 41 add_user or_else (e) cat ("kim again: ", e err_text) io.println end
cat ("kim: ", "kim" by_name has, ", park: ", "park" by_name has) io.println
"kim" forget
cat ("kim after free: ", "kim" by_name has) io.println
users do (r users) cat (r user.name, " ", r user.age) io.println end each
출력
$ pnx 08-index-pool.pnx
kim again: failed
kim: true, park: false
kim after free: false
lee 25
(4) 해시는 키가 있는 해시(SipHash-2-4)다. 키는 호스트가 준다(19.2). 밖에서 온 글을 키로 쓰는 호스트는 무작위 키를 줘야 한다 — 키를 아는 쪽은 충돌을 몰아 찾기를 느리게 만들 수 있다.
제약
(5) 풀 위의 찾아보기는 풀처럼 shared 가 아니다(8.13).
08-index-pool-shared.pnx
record user
age int
end
pool users user 4
shared index by_age users user.age
거절
$ pnx 08-index-pool-shared.pnx
08-index-pool-shared.pnx:5:21: an index over a pool is not shared, as a pool is not
진단
| 조건 | 메시지 |
|---|---|
| 레코드의 배열, 목록, 풀이 아닌 표 | an index belongs to a declared array, list or pool of records |
풀 위의 찾아보기 앞의 shared | an index over a pool is not shared, as a pool is not |
찾아보기가 아닌 것의 has | has takes a key and the name of an index: key by_name has |
8.11 앞선 선언
구문
forward = "forward" ( "array" | "list" | "pool" ) NAME
| "forward" "word" NAME "(" effect ")" ;(1) forward list 이름 은 뒤에서 선언할 표를 미리 알린다. 레코드가 자기를 담을 표의 줄을 필드로 가질 때 쓴다. 미리 알린 표의 이름은 타입 자리에만 쓴다. forward word 는 9.7 이 정한다.
08-forward.pnx
use io
use str
forward list nodes rem the record below points into this list
record node
value int
next nodes
end
list nodes node 4
row nodes b (2)
row nodes a (1, b)
a node.next node.value str.to_str io.println
a node.next node.next is_none if "the chain ends at b" io.println end
출력
$ pnx 08-forward.pnx
2
the chain ends at b
제약
(2) 미리 알린 표는 같은 모듈에서 같은 종류로 선언돼야 한다.
08-forward-missing.pnx
forward list nodes
record node
value int
end
거절
$ pnx 08-forward-missing.pnx
08-forward-missing.pnx:5:1: a table announced by forward is never declared
8.12 묶은 저장소
(1) bound 를 앞에 붙인 global array buffer 는 저장소를 호스트가 준다. 스크립트에서는 다른 전역과 같이 쓴다. 호스트가 묶기 전에는 상태 안의 자리를 쓴다. 묶는 법은 19.7 이 정한다.
8.13 공유 저장소
(1) shared 를 앞에 붙인 global array list buffer index 는 다른 상태의 저장소를 읽기만 한다. 선언한 상태에는 저장소가 없고, 호스트가 다른 상태의 같은 선언을 pinax_share 로 준다(19.7). 같은 자료를 여러 상태가 여러 스레드에서 한꺼번에 읽어도 복사도 잠금도 없다.
제약
(2) 공유 전역은 아무것도 바꾸지 못한다: 넣기, set, push, pop, clear, truncate, add_row, 필드에 넣기, insert, remove, 버퍼에 붙이는 낱말, map_into 와 filter_into 의 목적지가 모두 거절된다. 처리기는 공유 전역을 바꾸는 바이트코드도 적재 때 거절하고, 매개변수로 받은 공유 전역을 바꾸려는 바이트코드는 실행 때 결함 state 로 끝낸다.
08-shared-write.pnx
record city
name str(12)
people int
end
shared list cities city 1000
word grow (r cities) r city.people 1 add r to city.people end
거절
$ pnx 08-shared-write.pnx
08-shared-write.pnx:6:47: a shared global is read-only: nothing here may change it
(3) 공유 모음은 그 매개변수를 읽기만 하는 낱말의 모음 매개변수(9.9)로 넘길 수 있다. 낱말이 매개변수를 바꾸는 명령을 쓰거나, 매개변수를 바꾸는 낱말이나 호스트 낱말의 buffer 매개변수로 넘기면 그 매개변수를 바꾸는 낱말이다. 처리기는 원본을 다 읽은 뒤에 판단하므로 forward 로 알린 낱말에도, 앞선 적재의 낱말에도 같다.
08-shared-param.pnx
use io
use str
shared list scores int 100
word total (l list int gives int) l sum end rem reads its parameter only
word everyone (gives int) scores total end rem so a shared list may be given to it
scores total str.to_str io.println rem not given yet: empty
출력
$ pnx 08-shared-param.pnx
0
08-shared-change.pnx
shared list scores int 100
word reset (l list int) l clear end
word keep (l list int) l reset end rem gives it to a word that changes it
word start () scores keep end
거절
$ pnx 08-shared-change.pnx
08-shared-change.pnx:4:22: a shared collection is read-only: the word it is given to changes this parameter
(3a) 공유 모음은 호스트 낱말의 buffer 매개변수와 블록 값이 부르는 낱말(call)의 매개변수로는 넘기지 않는다. 받는 쪽이 무엇을 하는지 적재 때 모르기 때문이다. redefine 은 읽기만 하던 매개변수를 바꾸는 낱말로 만들지 못한다.
(4) shared 는 queue pool tasks 와 const record 앞에 오지 않는다.
08-shared-queue.pnx
shared queue jobs int 8
거절
$ pnx 08-shared-queue.pnx
08-shared-queue.pnx:1:8: shared stands before global, array, list, buffer or index
의미
(5) 호스트가 주기 전에는 비어 있다: 목록과 배열의 길이는 0 이고, 찾아보기는 아무것도 찾지 못하며, 스칼라와 버퍼는 0 과 빈 바이트다. 적재의 코드는 그대로 돈다. 호스트의 실행은 공유 전역을 모두 받을 때까지 거절된다(19.7).
8.14 실패 코드
(1) err 이름 은 실패 코드(failure code)를 만든다. err 타입의 새 오류 코드다(11.1). 상수(8.2)처럼 저장소가 없고, 그 이름은 제 모듈의 이름이다. 다른 모듈은 내보낸 것을 모듈.이름 으로 쓴다. 다른 코드를 쓰는 자리 어디에나 쓴다: fail 과 함께, or_else (e) 와 eq 로, case 의 가지 값으로, err 전역이나 모음 안에. err_text 는 그 이름을 준다.
(2) 상태가 선언한 실패 코드는 선언된 차례대로 0x400 부터 번호가 붙는다(19.1). 프로그램은 번호에 기대지 않는다. 호스트는 이름을 읽는다(pinax_error_text).
08-err.pnx
use io
use str
err key_too_long
err value_too_long
word check (k str, v str or fail)
k str.len 8 gt if key_too_long fail end
v str.len 16 gt if value_too_long fail end
end
word try (k str, v str)
k v check or_else (e)
e case
key_too_long of "the key is too long" end
value_too_long of "the value is too long" end
otherwise e err_text end
end io.println
return
end
"stored" io.println
end
"name" "value" try
"a long key name" "value" try
"name" "a value of thirty bytes, more" try
key_too_long err_text io.println
출력
$ pnx 08-err.pnx
stored
the key is too long
the value is too long
key_too_long
제약
(3) err 뒤에는 31 바이트 이하의 이름이 온다. 그 이름은 8.1 (3)과 (6)을 따른다.
08-err-name.pnx
err "too long"
거절
$ pnx 08-err-name.pnx
08-err-name.pnx:1:5: err is followed by the name of a new failure code
진단
| 조건 | 메시지 |
|---|---|
| 이미 있는 이름 | this name is already defined |
| 선언 앞에서 쓴 이름 | this name is not declared before this place |
호스트가 수를 주지 않은 host 용량(메시지에 선언의 이름) | the host gave no capacity for NAME (pinax_capacity) |
정의 밖의 to 가 전역이 아닌 이름에 | outside a definition `to` makes no new local; a global is written with set |
assign 의 값이 칸보다 많음 | assign has more values than the array has cells |
큐의 원소 타입이 int bool err dec(N) 밖 | a queue holds int, bool, err or dec(N) values |
alloc 이 막 준 줄의 push(8.8 (5a)) | push can fail and lose an owned row; store it with set, into a field or into a global |
row 의 값이 필드와 다름 | a value has another type than its field |
owned 줄을 넘기지 않는 경로 | an owned value in a local is not given on every path (close, free or store it) |
| 미리 알린 표가 없음 | a table announced by forward is never declared |
| 공유 전역을 바꿈 | a shared global is read-only: nothing here may change it |
| 공유 모음을 그것을 바꾸는 낱말에 넘김 | a shared collection is read-only: the word it is given to changes this parameter |
| 공유 모음을 블록 값의 낱말에 넘김 | a shared collection is read-only: it is not given to the word a block value names, which may change it |
redefine 이 읽던 매개변수를 바꿈 | redefine cannot make the word change a collection parameter the old one only read: shared collections may have been given to it |
| 공유 버퍼를 호스트 낱말에 넘김 | a shared buffer is read-only: it is not given to a host word, which may write it |
shared 뒤에 다른 선언 | shared stands before global, array, list, buffer or index |
이름이 따르지 않는 err | err is followed by the name of a new failure code |
| 31 바이트를 넘는 실패 코드의 이름 | the name of a failure code is at most 31 bytes long |