1 첫 프로그램

먼저 알아야 할 것: 없음. 이 장이 처음이다.

이 장이 답하는 물음

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 하나를 준다” 는 뜻이다. 몸이 끝날 때 스택에는 결과가 정확히 서명대로 남아 있어야 한다.

문답

물음. 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장에서 자세히 본다.

정리