1 첫 프로그램
먼저 알아야 할 것: 없음. 이 장이 처음이다.
이 장이 답하는 물음
- 가장 작은 Pinax 프로그램은 어떻게 생겼는가?
- 프로그램은 어떻게 돌리고, 잘못된 프로그램은 어떻게 알려 주는가?
- 낱말을 하나 만들어 부르려면 무엇을 쓰는가?
1.1 인사하는 프로그램
01-hello.pnx
use io
"Hello, Pinax!" io.println
출력
$ pnx 01-hello.pnx
Hello, Pinax!
이 두 줄이 프로그램 전체다. 첫 줄 use io 는 입출력 모듈 io 를 연다. 둘째 줄은 문자열 하나를 스택에 올리고, 낱말 io.println 을 부른다. io.println 은 스택 맨 위의 문자열을 내려 표준 출력에 쓰고 줄을 바꾼다.
Pinax 의 코드는 이렇게 값, 그다음 낱말의 차례로 읽는다. 값은 스택에 쌓이고, 낱말은 스택에서 필요한 만큼 가져가 일을 하고 결과를 다시 올린다. 이것을 뒤붙이기(postfix) 표기라고 한다.
처리기가 이 두 줄을 읽는 차례를 표로 따라가 보자. 왼쪽부터 토큰을 하나씩 읽고, 값이면 스택에 올리고 낱말이면 일을 시킨다. 스택은 맨 위를 오른쪽에 적었고, — 는 빈 스택이다.
| 토큰 | 그 뒤의 스택 | 일어난 일 |
|---|---|---|
use io | — | 모듈 io 를 연다. 스택은 건드리지 않는다. |
"Hello, Pinax!" | "Hello, Pinax!" | 문자열 값 하나가 스택에 올라간다. |
io.println | — | 맨 위의 문자열을 내려 출력하고 줄을 바꾼다. 스택이 다시 빈다. |
프로그램이 끝날 때 스택이 비어 있다는 점도 기억해 두자. 남는 값이 있으면 처리기는 그 프로그램을 거절한다(2장).
문답
물음. main 같은 시작 함수가 없는가?
답. 없다. 낱말 정의 밖에 적은 코드는 파일을 적재할 때 위에서 아래로 한 번 돈다. 그것이 프로그램의 시작이다. 큰 프로그램은 일을 낱말로 묶고 맨 끝에서 그 낱말을 부른다.
1.2 pnx 로 돌리기
명령 pnx 가 파일을 읽어 돌린다. 위 예의 출력 상자 첫 줄 $ pnx 01-hello.pnx 가 돌린 명령이다. pnx 는 파일을 돌리기 전에 먼저 적재한다: 토큰을 나누고, 이름과 타입과 스택을 모두 검사한다. 하나라도 틀리면 한 줄도 돌리지 않고 거절한다.
01-no-use.pnx
"Hello" io.println
거절
$ pnx 01-no-use.pnx
01-no-use.pnx:1:9: this path names neither a module in use nor a record
io 를 열지 않고 io.println 을 썼다. 메시지는 파일:줄:칸: 까닭 의 꼴이다. 칸은 문제의 토큰이 시작하는 자리다.
흔한 오해: + 로 더한다
1 + 2 를 쓰면 거절된다. Pinax 에서 리터럴과 주석 밖에 쓸 수 있는 글자는 이름의 글자와 ( ) , . 뿐이다. 더하기는 낱말 add 이고, 값 뒤에 쓴다.01-plus.pnx
use io
use str
1 + 2 str.to_str io.println
거절
$ pnx 01-plus.pnx
01-plus.pnx:3:3: a sign is attached to its number; arithmetic uses words (add, sub)
-7 처럼 숫자에 붙은 부호는 수의 일부라서 괜찮다. 떨어져 있는 + 와 - 만 안 된다.1.3 주석과 줄 바꿈
rem 으로 시작하는 줄은 주석이다. 코드 뒤에 rem 을 쓰면 그 줄의 나머지가 주석이 된다. 이 책의 예제는 주석으로 각 줄이 하는 일을 적어 둔다.
01-comments.pnx
rem A line that starts with rem is a comment: Pinax reads nothing on it.
use io
"one" io.println rem rem after code: the rest of the line is a comment
"two"
io.println rem a line break is only a space: one call over two lines
"three" io.println "four" io.println rem two calls on one line
출력
$ pnx 01-comments.pnx
one
two
three
four
줄 바꿈은 띄어쓰기와 같다. 낱말 하나를 부르는 코드가 두 줄에 걸쳐도 되고, 한 줄에 여러 일을 적어도 된다. 그래서 들여쓰기와 줄 나눔은 읽는 사람을 위한 것이다. 고르게 맞추려면 pnx fmt 을 쓴다(15장).
문답
물음. rem 은 왜 // 나 # 가 아닌가?
답. Pinax 에서 리터럴과 주석 밖에 쓸 수 있는 글자는 이름의 글자와 ( ) , . 뿐이다. 그래서 주석도 낱말처럼 생긴 rem 으로 연다. rem 은 예약 낱말이라 이름으로 쓰지 못한다.
1.4 낱말 만들기
1 부터 100 까지 더하는 프로그램을 세 가지로 써 보자.
01-sum.pnx
use io
use str
rem The sum of 1 to 100, three ways.
word sum_loop (n int gives int)
0 to total
1 to i
loop
i n gt until
total i add to total
i 1 add to i
end
total
end
word sum_range (n int gives int) 1 n 1 add range sum end
word sum_formula (n int gives int) n n 1 add mul 2 div end
100 sum_loop str.to_str io.println
100 sum_range str.to_str io.println
100 sum_formula str.to_str io.println
출력
$ pnx 01-sum.pnx
5050
5050
5050
word 이름 (서명) 몸 end 가 낱말 하나를 정의한다. 서명 (n int gives int) 는 “int 하나를 n 이라는 이름으로 받아 int 하나를 준다” 는 뜻이다. 몸이 끝날 때 스택에는 결과가 정확히 서명대로 남아 있어야 한다.
sum_loop는 지역 변수total과i를 쓰는 루프다.to 이름이 맨 위 값을 그 이름에 넣는다.loop … end는 되풀이하고,i n gt until은 “i가n보다 크면 떠난다” 이다.sum_range는1 n 1 add range로 1 부터n까지의 줄기를 만들어sum에 준다.sum_formula는 공식 를 뒤붙이기로 적은 것이다.
문답
물음. str.to_str 은 왜 붙는가?
답. io.println 은 문자열만 받는다. 정수를 출력하려면 먼저 글로 바꿔야 하고, 그 낱말이 모듈 str 의 to_str 이다. 그래서 use str 도 있다. 정수를 그대로 받아 주는 출력 낱말은 없다 — 낱말마다 서명이 하나뿐이기 때문이다.
1.5 인자 받기
pnx 파일 인자… 의 인자는 모듈 env 로 읽는다.
01-greet.pnx
use io
use env
word main ()
1 env.arg or_else (e) "stranger" end to name rem argument 1, or a word for nobody
cat ("Hello, ", name, "!") io.println
end
main
출력
$ pnx 01-greet.pnx Pinax
Hello, Pinax!
1 env.arg 는 첫 인자를 준다. 인자가 없을 수도 있으므로 이 낱말은 실패할 수 있다. 그래서 바로 뒤에 or_else … end 를 써서 실패했을 때 대신 쓸 값을 적었다. 실패할 수 있는 낱말을 부르고 그 뒤를 비워 두면 거절된다. 실패는 8장에서 자세히 본다.
정리
- 코드는 값, 그다음 낱말의 차례다. 정의 밖의 코드는 적재할 때 한 번 돈다(표준 5.2).
- 모듈의 낱말은
use 모듈을 쓰고모듈.낱말로 부른다(13.1). +;같은 글자는 없다. 산술은 낱말이다(6.1, 6.4).word 이름 (서명) … end가 낱말을 만든다. 몸이 끝나면 스택이 서명의 결과와 같아야 한다(9.2).- 잘못된 프로그램은 돌기 전에 거절된다(4).