9 낱말
9.1 정의
구문
definition = [ "export" ] "word" NAME "(" effect ")" { body } "end" ;
effect = [ params ] [ "gives" params ] [ "or" "fail" ] ;
params = param { "," param } ;
param = [ NAME ] ( type | collection-type ) ; (* collection-type: 9.9 *)
type = [ "owned" ] ( NAME [ "(" size ")" ] | "word" "(" effect ")" ) ;(1) word 이름 (서명) 몸 end 는 낱말을 정의한다. 낱말은 정의 밖, 소스의 맨 바깥에서만 정의한다. 낱말 안에 낱말을 정의하지 못한다.
(2) 낱말을 부른다(call)는 것은 몸에서 그 이름을 쓰는 것이다. 부르는 자리에서 스택의 맨 위 값들이 낱말의 입력이 되고, 낱말이 끝나면 결과가 그 자리에 놓인다. 핵 낱말, 표준 모듈의 낱말, 호스트 낱말도 같은 모양으로 부른다.
9.2 서명
(1) 모든 낱말에는 서명이 있다. 입력, gives 와 결과, 그리고 실패할 수 있으면 or fail 을 이 차례로 적는다. 입력도 결과도 없으면 () 다. 서명은 적재 때 검사된다: 부르는 자리의 스택은 입력과 맞아야 하고, 몸이 끝나는 자리의 스택은 결과와 맞아야 한다.
(2) 입력에 이름을 쓰면(w int) 그 값은 그 이름의 지역 변수가 되고 몸은 빈 스택으로 시작한다. 타입만 쓰면(int) 값은 스택에 놓인 채 몸이 시작한다. 결과 쪽의 이름은 읽는 사람을 위한 표시일 뿐 지역 변수를 만들지 않는다. 입력은 모음을 받을 수도 있다(9.9).
09-signatures.pnx
use io
use str
word area (w int, h int gives a int) w h mul end rem named inputs become locals
word square (int gives int) dup mul end rem a bare type: the value stays on the stack
global total int
word reset () 0 set total end rem neither inputs nor results
3 4 area str.to_str io.println
5 square str.to_str io.println
reset
출력
$ pnx 09-signatures.pnx
12
25
제약
(3) 한 서명의 입력은 모두 이름이 있거나 모두 타입만 있어야 한다.
09-signature-mixed.pnx
word area (w int, int gives int) w mul end
거절
$ pnx 09-signature-mixed.pnx
09-signature-mixed.pnx:1:32: inputs are all named or all bare types, not a mix
(4) 몸이 끝나는 자리(그리고 return 하는 자리)에서 스택에는 결과가 정확히 그 차례와 타입으로 있어야 한다. 더 있어도 모자라도 거절된다.
09-result-missing.pnx
word half (n int gives r int) n 2 div drop end
거절
$ pnx 09-result-missing.pnx
09-result-missing.pnx:1:44: the stack does not hold exactly the results of the signature here
9.3 스택
(1) 낱말과 낱말 사이의 값은 스택에 놓인다. 리터럴과 이름 읽기는 값 하나를 올리고, 낱말은 입력을 내리고 결과를 올린다. 처리기는 적재 때 몸의 모든 자리에서 스택의 깊이와 칸마다의 타입을 안다.
(2) 스택을 고치는 핵 낱말은 다섯뿐이다.
| 낱말 | 전 → 후 | 뜻 |
dup | a → a a | 맨 위를 하나 더 |
drop | a → | 맨 위를 버림 |
swap | a b → b a | 위의 둘을 바꿈 |
over | a b → a b a | 둘째를 위로 하나 더 |
nip | a b → b | 둘째를 버림 |
이보다 깊은 조작(rot pick roll depth)은 없다. 값이 셋 이상 얽히면 지역 변수를 쓴다.
09-stack-words.pnx
use io
use str
word show (a int, b int) cat (a, " ", b) io.println end
1 2 swap show rem 2 1
1 2 over drop show rem over copies the second: 1 2 1, drop gives 1 2
1 2 3 nip show rem nip drops the second: 1 3
4 dup show rem 4 4
출력
$ pnx 09-stack-words.pnx
2 1
1 2
1 3
4 4
참고
스택을 처음 보는 사람을 위해 2 3 add 4 mul 을 한 토큰씩 따라가면 이렇다(맨 위는 오른쪽).
| 토큰 | 그 뒤의 스택 | 일어난 일 |
|---|---|---|
2 | 2 | 값을 올린다. |
3 | 2 3 | 값을 올린다. |
add | 5 | 두 값을 내려 합을 올린다. |
4 | 5 4 | 값을 올린다. |
mul | 20 | 두 값을 내려 곱을 올린다. |
처리기는 적재 때 이 표의 둘째 칸(깊이와 타입)을 모든 자리에서 미리 안다. 그래서 (3) 과 (3a) 의 잘못은 돌기 전에 드러난다.
제약
(3) 낱말이 받을 값이 스택에 모자라면 거절된다. 스택의 깊이는 돌 때 달라지지 않는다.
09-stack-short.pnx
word f (int gives int) add end
거절
$ pnx 09-stack-short.pnx
09-stack-short.pnx:1:24: the stack does not hold enough values for this word
(3a) 정의 밖의 코드(5.2)가 끝날 때, 곧 소스의 끝에서 스택에 값이 남으면 거절된다. 낱말의 몸이 끝날 때의 스택은 9.2 가 정한다.
09-leftover.pnx
use io
"done" io.println
1 2 add rem a value that nothing takes
거절
$ pnx 09-leftover.pnx
09-leftover.pnx:4:1: values are left on the stack at the end of the source
(4) 정의되지 않은 이름을 쓰면 거절된다.
09-unknown-word.pnx
3 4 plus drop
거절
$ pnx 09-unknown-word.pnx
09-unknown-word.pnx:1:5: this name is not declared before this place
9.4 지역 변수
구문
store = "to" ( NAME | PATH | "(" NAME { "," NAME } ")" ) ;
set = "set" ( NAME | PATH ) ;(1) 몸 안에서 to 이름 은 맨 위 값을 내려 그 이름에 넣는다. 그 이름이 처음이면 그 값의 타입으로 지역 변수(local variable)가 생긴다. 그 뒤의 to 는 같은 타입이어야 한다. to 는 지역 변수(와 to r.필드 의 필드)에만 넣는다.
(1a) 전역에는 set 이름 으로 넣는다. v set 이름 은 스칼라 전역에, v i set 이름 은 배열과 목록의 칸에 넣는다. 다른 모듈이 내보낸 전역은 set 모듈.이름 이다. 정의 안팎이 같다. to 가 지역을 만드는 일과 전역을 바꾸는 일을 글자로 가르려는 규칙이다.
(2) to (a, b, c) 는 위의 값 셋을 한 번에 넣는다. 오른쪽 이름이 맨 위 값을 받는다 — 서명에 적은 차례와 같다.
09-locals.pnx
use io
use str
word stats (a int, b int, c int gives s str)
a b add c add to total rem the first `to` makes a local of that type
total 3 div to mean
a b max c max to top
cat ("total ", total, ", mean ", mean, ", max ", top)
end
word split3 (gives int, int, int) 1 2 3 end
3 9 6 stats io.println
word show3 () split3 to (x, y, z) cat (x, y, z) io.println end rem the rightmost is the top
show3
출력
$ pnx 09-locals.pnx
total 18, mean 6, max 9
123
09-store-global.pnx
use io
use str
global calls int
word tick () calls 1 add set calls end rem set stores into a global; to only into locals
tick tick tick
calls str.to_str io.println
출력
$ pnx 09-store-global.pnx
3
(3) 지역 변수가 보이는 범위는 처음 넣은 자리부터 그것을 둘러싼 가지, 루프, 블록의 끝까지다. 바깥에서 만든 지역 변수에는 안에서 넣을 수 있다.
제약
(4) 지역 변수에 처음과 다른 타입의 값을 넣으면 거절된다.
09-local-type.pnx
word f (gives s str)
5 to v
"five" to v
v
end
거절
$ pnx 09-local-type.pnx
09-local-type.pnx:3:13: this local variable has another type than the value stored into it
(5) 가지 안에서 처음 만든 지역 변수는 가지 밖에서 읽지 못한다. 다른 가지로 왔다면 값이 없기 때문이다.
09-local-scope.pnx
word f (flag bool gives int)
flag if 1 to v end
v
end
거절
$ pnx 09-local-scope.pnx
09-local-scope.pnx:3:3: this name is not declared before this place
(6) to 의 이름이 전역이면 거절된다. 전역은 가릴 수 없으므로(9.5) 그 이름의 지역을 만들 수도 없고, 전역을 조용히 바꾸지도 않는다. 메시지가 set 을 알려 준다. set 의 이름이 전역이 아니면 거절된다.
09-store-global-bare.pnx
global total int
word add_up (n int) total n add to total end rem total is a global: set total
거절
$ pnx 09-store-global-bare.pnx
09-store-global-bare.pnx:2:36: this name is a global; a global is written with set: value set name
9.5 가림 금지
(1) 새 이름(매개변수, 지역 변수, 루프의 이름)은 그 자리에서 보이는 이름 — 낱말, 전역, 상수, 타입, 바깥의 지역 변수 — 과 같을 수 없다. 이름 하나가 한 자리에서 늘 한 가지만 뜻하게 하려는 규칙이다.
제약
(2) 보이는 이름과 같은 매개변수나 지역 변수는 거절된다.
09-shadow.pnx
global limit int
word clip (n int, limit int gives int) n limit min end
거절
$ pnx 09-shadow.pnx
09-shadow.pnx:2:40: this name is already visible here; a new name may not hide it
09-shadow-word.pnx
word f (add int gives int) add 1 add end
거절
$ pnx 09-shadow-word.pnx
09-shadow-word.pnx:1:28: add is a word of the language; choose another name
(3) 언어의 낱말 — 예약어, 핵심 낱말, 모듈 없이 쓰는 15절과 16절의 낱말, 타입 — 은 어디서도 새 이름이 될 수 없다. 낱말, 선언, 매개변수, 지역 변수, 루프의 이름이 모두 그렇다. 예약어는 필드의 이름도 될 수 없다. 다른 언어의 낱말은 필드의 이름이 될 수 있다. 필드는 늘 레코드 뒤에 쓰기 때문이다(8.8). 메시지는 그것이 어느 낱말인지 말한다.
09-name-taken.pnx
word swap (a int, b int gives b int, a int) b a end
거절
$ pnx 09-name-taken.pnx
09-name-taken.pnx:1:6: swap is a word of the language; choose another name
9.6 되부름과 꼬리 부름
(1) 낱말은 자기를 부를 수 있다. 앞선 선언은 필요 없다.
(2) 낱말의 마지막 일로 다른 낱말을 부르는 꼬리 부름(tail call)은 부른 낱말의 자리를 다시 쓴다. 그래서 꼬리 부름으로 도는 되부름은 깊이가 자라지 않는다. 따로 표시하지 않는다.
09-recursion.pnx
use io
use str
word fact (n int gives int) n 1 le if 1 else n n 1 sub fact mul end end
word count_down (n int, acc int gives int) rem a call in tail place reuses the frame
n 0 eq if acc return end
n 1 sub acc 1 add count_down
end
20 fact str.to_str io.println
1000000 0 count_down str.to_str io.println
출력
$ pnx 09-recursion.pnx
2432902008176640000
1000000
결함
(3) 꼬리 부름이 아닌 부름이 쌓여 호출 깊이의 한계(부속서 C)를 넘으면 결함 depth 다.
09-depth.pnx
word deep (n int gives int) n 0 eq if 0 else n 1 sub deep 1 add end end
1000000 deep drop
결함으로 멈춤
$ pnx 09-depth.pnx
09-depth.pnx:1: call depth limit
n int = 983617
9.7 앞선 낱말 선언
(1) forward word 이름 (서명) 은 뒤에서 정의할 낱말을 미리 알린다. 서로를 부르는 두 낱말에 쓴다.
09-forward-word.pnx
use io
forward word is_odd (n int gives bool)
word is_even (n int gives bool) n 0 eq if true else n 1 sub is_odd end end
word is_odd (n int gives bool) n 0 eq if false else n 1 sub is_even end end
7 is_even if "7 is even" else "7 is odd" end io.println
출력
$ pnx 09-forward-word.pnx
7 is odd
제약
(2) 정의의 서명은 미리 알린 서명과 같아야 한다. 같은 모듈에서 정의되지 않으면 거절된다.
09-forward-other.pnx
forward word f (n int gives int)
word f (n int gives bool) n 0 eq end
거절
$ pnx 09-forward-other.pnx
09-forward-other.pnx:2:27: the definition has another signature than its forward declaration
9.8 글 잇기
구문
cat = "cat" "(" phrase { "," phrase } ")" ;
phrase = body { body } ; (* code that leaves one value *)(1) cat ( … ) 은 쉼표로 가른 항목을 이어 str 하나를 낸다. 항목마다 값 하나를 남기는 코드다. str 은 그대로, int 는 십진으로, bool 은 true false 로 들어간다. 다른 모양(십육진 등)은 그 모양을 만드는 낱말(16.2)로 먼저 str 을 만든다.
(2) 결과는 구역(12.2)에 만든 뷰다. 항목의 수는 글에 보이는 만큼이고, 처리기는 길이를 먼저 더해 한 번에 쓴다.
09-cat.pnx
use io
use fmt
word show (id int, name str, ok bool) cat ("id=", id, " name=", name, " ok=", ok, " hex=", id fmt.hex) io.println end
255 "kim" true show
출력
$ pnx 09-cat.pnx
id=255 name=kim ok=true hex=ff
9.9 모음 매개변수
구문
collection-type = ( "array" | "list" | "queue" ) cell | "buffer" ;
cell = NAME [ "(" size ")" ] ; (* int, bool, err, dec(N), str(N), 레코드, 또는 표: 그 줄 *)(1) 타입이 array T, list T, queue T 나 buffer 인 입력은 모음을 받는다. 부르는 쪽은 그 자리에 모음의 이름을 쓴다(scores total). 몸 안에서 매개변수는 그런 모음의 이름이 들어가는 모든 자리에 쓴다 — l len, i l idx, v l push, v i set l, l do … end each, … end l map_into, q take, s b append 따위. 칸은 매개변수가 말하는 타입이어야 한다: int bool err, N 이 같은 dec(N) 과 str(N), 또는 레코드. 용량은 타입에 들지 않는다. 어떤 용량이든 받고, l capacity 가 그 용량을 준다.
09-coll-param.pnx
use io
list scores int 8
list bonus int 8
queue jobs int 4
array grid int 3
word add_range (l list int, lo int, hi int) rem lo, lo + 1, ... up to hi - 1
lo hi range do (v int) v l push or_else (e) end end each
end
word summary (l list int gives str) cat (l len, " of ", l capacity, ", sum ", l sum) end
word fill (q queue int, n int) 0 n range do (v int) v q put or_else (e) end end each end
word zero (a array int) a do (v int, i int) 0 i set a end each_index end
word demo ()
scores 1 4 add_range bonus 10 12 add_range
scores summary io.println
bonus summary io.println
jobs 3 fill cat ("jobs: ", jobs len) io.println
7 1 set grid grid zero cat ("grid: ", 1 grid idx) io.println
end
demo
출력
$ pnx 09-coll-param.pnx
3 of 8, sum 6
2 of 8, sum 21
jobs: 3
grid: 0
(2) 매개변수 l 이 레코드 표인 낱말에서는, 선언된 표의 이름처럼(8.8) 이름 l 이 그 줄의 타입이기도 하다. l 로 얻은 줄은 그 낱말 안에서 쓴다: 지역 변수, 필드, 고리의 블록에서.
09-coll-param-rows.pnx
use io
record city
name str(12)
people i32
end
list north city 4
list south city 4
word add_city (l list city, s str, n int or fail)
l add_row or_fail to r rem r is a row of l
s r to city.name n i32 r to city.people
end
word largest (l list city gives str)
"" to best -1 to most
l do (r l) r city.people raw most gt if r city.people raw to most r city.name to best end end each
best
end
word demo (or fail)
north "seoul" 9 add_city or_fail north "incheon" 3 add_city or_fail
south "busan" 3 add_city or_fail
cat (north largest, " ", south largest) io.println
end
demo or_else (e) end
출력
$ pnx 09-coll-param-rows.pnx
seoul busan
(2a) map_into 와 filter_into(10.6)는 매개변수가 가리키는 목록이나 배열에 담고, 매개변수가 가리키는 표에서 줄을 베낀다.
09-coll-param-fill.pnx
use io
record city
name str(12)
people i32
end
list cities city 4
row cities ("seoul", 9)
row cities ("busan", 3)
row cities ("ulsan", 1)
list big city 4
list counts int 4
list high int 4
word above (l list int, n int, out list int or fail)
l do (x int gives bool) x n gt end out filter_into or_fail
end
word large (l list city, n int or fail)
l do (r l gives bool) r city.people raw n gt end big filter_into or_fail rem rows of l copied
end
word demo (or fail)
cities do (r cities gives int) r city.people raw end counts map_into or_fail
counts 2 high above or_fail
cities 2 large or_fail
cat (high len, " ", big len, " ", 0 big idx city.name) io.println
end
demo or_else (e) e err_text io.println end
출력
$ pnx 09-coll-param-fill.pnx
2 2 seoul
(3) 넘기는 것은 모음 그 자체이고 복사본이 아니다. 모음은 전역이므로 매개변수를 yield 와 14.3 의 기다림을 건너 쥐고 있어도 된다.
제약
(4) 모음은 그 종류와 칸이 매개변수의 것과 같은 자리에 주어야 한다.
09-coll-param-kind.pnx
list names str(8) 4
word total (l list int gives int) l sum end
word f (gives int) names total end
거절
$ pnx 09-coll-param-kind.pnx
09-coll-param-kind.pnx:3:26: the collection given here is of another kind, or holds another type, than the parameter says
(5) 모음 매개변수는 값이 아니다: to 로 담지 못하고, 낱말의 결과가 되지 못하고, 블록(word (…))의 매개변수가 되지 못한다. 풀과 찾아보기는 받지 않는다.
09-coll-param-result.pnx
word pick (a list int, b list int gives list int) a end
거절
$ pnx 09-coll-param-result.pnx
09-coll-param-result.pnx:1:41: a word gives no collection; it takes one as a parameter
(6) 목록 매개변수를 자르는(8.5 (2a)) 낱말, 또는 자르는 낱말에 그것을 넘기는 낱말은 받은 목록을 자른다. 그러면 그 낱말을 부르는 자리에서 그 목록에 8.5 (2b) 의 규칙이 적용된다.
09-coll-param-truncate.pnx
record city
name str(12)
end
list cities city 8
global capital cities rem keeps a row of cities
word reset (l list city) 0 l truncate end
word f () cities reset end
거절
$ pnx 09-coll-param-truncate.pnx
09-coll-param-truncate.pnx:7:18: this list's rows are kept by a field, a global, a collection, a named row or an index; the word it is given to truncates it
(7) filter_into 는 두 표가 모두 매개변수일 때 줄을 베끼지 않는다. 한쪽을 each 로 돌며 add_row 로 담는다.
09-coll-param-copy.pnx
record city
name str(12)
end
word keep (src list city, dst list city or fail)
src do (r src gives bool) true end dst filter_into or_fail rem both tables are parameters
end
거절
$ pnx 09-coll-param-copy.pnx
09-coll-param-copy.pnx:5:7: filter_into does not copy rows between two tables parameters name; walk one and add_row
진단
| 조건 | 메시지 |
|---|---|
| 입력에 이름 있는 것과 없는 것이 섞임 | inputs are all named or all bare types, not a mix |
| 끝나는 자리의 스택이 결과와 다름 | the stack does not hold exactly the results of the signature here |
| 스택의 값이 모자람 | the stack does not hold enough values for this word |
| 소스의 끝에 값이 남음 | values are left on the stack at the end of the source |
| 정의되지 않은 이름 | this name is not declared before this place |
| 지역 변수의 타입이 바뀜 | this local variable has another type than the value stored into it |
to 의 이름이 전역 | this name is a global; a global is written with set: value set name |
to 모듈.이름 으로 전역에 | a global of a module is written with set: value set module.name |
set 의 이름이 지역 | set writes a global; a local is written with to |
set 의 이름이 전역이 아님 | set writes a declared global; there is none of this name |
| 보이는 이름을 가림 | this name is already visible here; a new name may not hide it |
| 언어의 낱말을 새 이름으로 씀(메시지에 그 낱말) | NAME is a word of the language; choose another name |
| 앞선 선언과 다른 서명 | the definition has another signature than its forward declaration |
| 종류나 칸이 다른 모음 | the collection given here is of another kind, or holds another type, than the parameter says |
| 모음 자리에 값 | this parameter takes an array, list, queue or buffer: give its name |
| 결과로 모음 | a word gives no collection; it takes one as a parameter |
모음 매개변수에 to | this is a collection parameter; a cell of it is stored as: value index set name |
| 남이 쥔 목록을 자르는 낱말에 줌 | this list's rows are kept by a field, a global, a collection, a named row or an index; the word it is given to truncates it |
| 두 표가 모두 매개변수인 줄 베끼기 | filter_into does not copy rows between two tables parameters name; walk one and add_row |