1 The first program
Builds on: Nothing; this is the first chapter.
Questions this chapter answers
- What does the smallest Pinax program look like?
- How is a program run, and how is a wrong program reported?
- What does it take to make a word and call it?
1.1 A program that says hello
01-hello.pnx
use io
"Hello, Pinax!" io.println
Output
$ pnx 01-hello.pnx
Hello, Pinax!
These two lines are the whole program. The first line, use io, opens the module io for input and output. The second puts a string on the stack and calls the word io.println, which takes the string off the top of the stack, writes it to standard output and ends the line.
Pinax code reads in this order: values, then the word. Values pile up on the stack; a word takes off what it needs, does its work and puts its results back. This is called postfix notation.
Follow the order in which the processor reads these two lines. It reads one token at a time from the left: a value goes onto the stack, a word is set to work. The top of the stack is written on the right, and — is an empty stack.
| Token | Stack after it | What happened |
|---|---|---|
use io | — | Opens the module io. The stack is not touched. |
"Hello, Pinax!" | "Hello, Pinax!" | One string value goes onto the stack. |
io.println | — | Takes the string off the top, prints it and ends the line. The stack is empty again. |
Note that the stack is empty when the program ends. A program that leaves values on it is refused (chapter 2).
Q&A
Q. Is there no start function like main?
A. No. Code written outside word definitions runs once, from top to bottom, when the file is loaded; that is where a program starts. A larger program gathers its work into words and calls one of them at the very end.
1.2 Running with pnx
The command pnx reads a file and runs it. The first line of the output box above, $ pnx 01-hello.pnx, is the command that was run. Before running a file, pnx loads it: it splits the tokens and checks every name, type and stack. If anything is wrong, it refuses without running a single line.
01-no-use.pnx
"Hello" io.println
Refused
$ pnx 01-no-use.pnx
01-no-use.pnx:1:9: this path names neither a module in use nor a record
This program uses io.println without opening io. A message has the form file:line:column: reason; the column is where the offending token starts.
Common mistake: Adding with +
1 + 2, as in other languages, is refused. Outside literals and remarks Pinax allows only the characters of names and ( ) , .. Addition is the word add, written after the values.01-plus.pnx
use io
use str
1 + 2 str.to_str io.println
Refused
$ pnx 01-plus.pnx
01-plus.pnx:3:3: a sign is attached to its number; arithmetic uses words (add, sub)
-7, is part of the number and fine. Only a + or - standing apart is not.1.3 Remarks and line breaks
A line that starts with rem is a remark. rem after code makes the rest of the line a remark. The examples of this book use remarks to say what each line does.
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
Output
$ pnx 01-comments.pnx
one
two
three
four
A line break is the same as a space. The code of one call may run over two lines, and one line may hold several pieces of work. Indentation and line breaks are for the reader; pnx fmt evens them out (chapter 15).
Q&A
Q. Why rem and not // or #?
A. Outside literals and remarks, Pinax allows only the characters of names and ( ) , .. So a remark also opens with a word-like rem. rem is reserved and cannot be a name.
1.4 Making words
Here is a program that adds up 1 to 100, three ways.
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
Output
$ pnx 01-sum.pnx
5050
5050
5050
word NAME (signature) body end defines a word. The signature (n int gives int) means “takes one int, named n, and gives one int”. When the body ends, the stack shall hold exactly the results the signature promises.
sum_loopis a loop with the localstotalandi.to NAMEputs the top value into that name.loop … endrepeats, andi n gt untilmeans “leave wheniis greater thann”.sum_rangemakes a stem of 1 tonwith1 n 1 add rangeand gives it tosum.sum_formulais the formula written in postfix.
Q&A
Q. Why the str.to_str?
A. io.println takes only strings. To print an integer it has to become text first, and the word for that is to_str of the module str — hence use str. There is no output word that takes an integer as it is: each word has exactly one signature.
1.5 Taking arguments
The arguments of pnx FILE ARGUMENTS… are read with the module 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
Output
$ pnx 01-greet.pnx Pinax
Hello, Pinax!
1 env.arg gives the first argument. There may be none, so this word can fail that is why or_else … end follows it right away, with the value to use instead. Calling a word that can fail and leaving the place after it empty is refused. Chapter 8 covers failures.
Summary
- Code is values, then words. Code outside definitions runs once at load (standard 5.2).
- Words of a module need
use MODULEand are writtenMODULE.WORD(13.1). - There are no characters like
+or;; arithmetic is words (6.1, 6.4). word NAME (signature) … endmakes a word; when the body ends the stack shall match the results (9.2).- A wrong program is refused before it runs (4).