20 The command pnx

(1) pnx is a host that uses this library, and a development tool. It registers all the standard host modules (clause 17).

pnx [--lib NAME=PATH]... [--capacity NAME=N]... file.pnx [arguments]
pnx test file.pnx
pnx repl
pnx see file.pnx
pnx fmt file.pnx
pnx export-c file.pnx [NAME]

20.1 Running

(1) pnx file.pnx arguments… loads the file and runs its load code. For each module the file uses that is not registered, it first loads MODULE.pnx from beside the file (13.1). The file is loaded as the module named after it without .pnx; when that name is not written like a word (it begins with a digit or contains -), the module has no name.

(2) There are three exit statuses, and the one a program gives with sh.exit (17.5).

StatusWhen
0the load code ran to its end; for pnx test, every test passed
1a refusal: file:line:column: reason on standard error; a fault: file:line: kind and the locals of the word; for pnx test, a test failed
2the command line is wrong (the usage is shown), the file cannot be read, a library of --lib cannot be opened, a --capacity is not NAME=N

(3) --lib NAME=PATH opens the shared library at PATH so that the resolver finds its functions for extern … from "NAME" (clause 18). It finds only the functions that library defines (not those it imports). Without --lib, every extern is refused.

(3a) --capacity NAME=N gives the capacity a source writes host (8 (4a), 19.4 (1d)), for a run, pnx test, pnx see, pnx export-c and pnx repl. A plain NAME is a declaration of the file, MODULE.NAME one of a module it uses. N is 1 to 231 − 1, and at most 16 are given. Without it, a source that writes host is refused at load, with the name of the capacity.

(3b) What the script writes with io.print and io.println is kept in a buffer and written when the buffer is full, before io.eprint and clock.sleep, before io.read_line asks the system for more of standard input, and when the run ends or faults. So standard output and standard error come out in the order the script wrote them, and a prompt shows before the program waits for a line. Except in pnx repl, io.read_line reads standard input ahead; a line already read ahead is given without writing. A host that embeds the library writes and reads both without a buffer.

20.2 Tests

Syntax
test = "test" NAME { body } "end" ;

(1) test NAME … end defines a test: a word that takes and gives nothing. Tests do not run at load. pnx test file.pnx runs the tests of the file in order, writes ok or FAIL for each, and exits with 0 when all pass, 1 otherwise. A fault inside a test (a false assert included) is that test’s failure.

20-test.pnx

use str
word middle (s str gives str) s 1 str.skip 1 str.skip_last end
test middle_trims_both_ends
  "[abc]" middle "abc" eq assert
end
test middle_of_two_is_empty
  "ab" middle str.is_empty assert
end

Test run

$ pnx test 20-test.pnx
ok   middle_trims_both_ends
ok   middle_of_two_is_empty
2 tests, 0 failed

20-test-fail.pnx

word twice (n int gives int) n 2 mul end
test doubles 21 twice 42 eq assert end
test wrong 21 twice 43 eq assert end

test-fail

$ pnx test 20-test-fail.pnx
ok   doubles
FAIL wrong (failed)
2 tests, 1 failed

20.3 Sessions

(1) pnx repl reads lines from standard input and runs them one at a time (pinax_eval of 19.4). After each line it writes the session stack on standard output, the deepest value first, when it is not empty. A line can use what earlier lines defined and the values they left (19.4 (2a)). A line that ends inside a definition or a block is read together with the next. A line that is refused or ends with a fault leaves the stack as it was. The end of a line, Enter, is a dot (10.8): what the line wrote is on the screen before the prompt comes back.

(2) There are tool words only for sessions: list_words (the words defined), stack (the types on the stack there), clear (at the start of a line: the line starts from an empty stack), see (the stack after every line of the definitions that follow), redefine word … (the same as word … for a word that exists, (2a)).

(2a) word NAME … with the name of a word an earlier line of the session defined redefines it. With the same signature, every word that calls it calls the new one. With another signature the new word takes the name; the words compiled before keep calling the old body, which no longer has a name, and pnx repl names them (“2 words still call the old sq: quad, cube”). One word is redefined in a line, and none while a task is alive. A word of a module loaded from a file is not redefined in the session: edit the file and load it again.

20-repl.pnx

use str
3 4
mul
word sq (n int gives int) n n mul end
sq
"x" 3 str.repeat
1 0 div
clear
2 sq

Session

$ pnx repl
3 4
12
12
144
144 "xxx"
error: line 1: div_zero
4

20.4 Showing the stack, and formatting

(1) pnx see file.pnx shows, for every line of a definition, the stack (as types) where that line ends.

20-see.pnx

use str
word tally (s str gives n int, longest int)
  0 to n
  0 to longest
  s str.words do (w str)
    n 1 add to n
    w str.len
    longest max to longest
  end each
  n longest
end

Listing

$ pnx see 20-see.pnx
    3    0 to n                                         | (empty)
    4    0 to longest                                   | (empty)
    5    s str.words do (w str)                         | (empty)
    6      n 1 add to n                                 | (empty)
    7      w str.len                                    | int
    8      longest max to longest                       | (empty)
    9    end each                                       | (empty)
   10    n longest                                      | int int

(1a) When the source is refused, pnx see shows the lines before the place of the refusal, gives the diagnostic, and exits with 1.

20-see-refused.pnx

word f (n int gives int)
  n 1 add
  n
end

Listing, then the refusal

$ pnx see 20-see-refused.pnx
    2    n 1 add                                        | int
    3    n                                              | int int
20-see-refused.pnx:4:1: the stack does not hold exactly the results of the signature here

(1b) pnx see --checks file.pnx loads the file as pnx file.pnx does (the code outside definitions runs), then shows, per word, the run-time checks the loader proved cannot fire: the word’s number of checks and of proven ones, and for each proven instruction its line, the instruction and the reason. A proof changes nothing a program does; the checks still run.

20-see-checks.pnx

use io
use str
const days 7
array rain int days
record city
  name str(8)
  people int
end
list cities city 4
word wettest (gives int)                      rem the index of the largest cell
  0 to best
  0 rain len range do (i int)
    i rain idx  best rain idx  gt if i to best end
  end each
  best
end
word people_of (i int gives int) i cities idx city.people end
assign rain (3, 0, 12, 7, 0, 1, 2)
wettest str.to_str io.println

Output, then the checks

$ pnx see --checks 20-see-checks.pnx
2
word wettest: 3 checks, 3 proven
  line 13: AGET: the index lies below the capacity of the array
  line 13: AGET: the index lies below the capacity of the array
  line 14: ADDI: the operands are in ranges whose result fits
word people_of: 2 checks, 1 proven
  line 17: FGET: the row passed this table's check before
word (load): 7 checks, 7 proven
  line 18: ASET: the index lies below the capacity of the array
  line 18: ASET: the index lies below the capacity of the array
  line 18: ASET: the index lies below the capacity of the array
  line 18: ASET: the index lies below the capacity of the array
  line 18: ASET: the index lies below the capacity of the array
  line 18: ASET: the index lies below the capacity of the array
  line 18: ASET: the index lies below the capacity of the array

(1c) pnx see --effects file.pnx loads the file without running it and shows, for each word of the file, what it may do beyond its stack and locals (pinax_word_effects, 19.5 (5)): the globals it reads and writes, the host modules it reaches and whether it reads or writes through them, “anything (a C function)” for extern, “may yield” and “may fail”; “none” for a word that uses only its stack and locals.

20-see-effects.pnx

use io
global total int
word sq (n int gives int) n n mul end
word add_up (n int) total n add set total end
word report () cat ("total ", total sq) io.println end
word ask (gives line str or fail) io.read_line or_fail end

The effects of each word

$ pnx see --effects 20-see-effects.pnx
sq: none
add_up: reads total; writes total
report: reads total; io writes
ask: io reads; may fail

(2) pnx fmt file.pnx writes the text with only indentation and spaces changed to standard output. Tokens, line breaks and remarks stay where they are. Formatting the result again changes nothing.

20-fmt.pnx

use io
word sign (n int gives r int)
n 0 gt if
1
   else   
n 0 lt if -1 else 0 end
end
end

Formatted

$ pnx fmt 20-fmt.pnx
use io
word sign (n int gives r int)
  n 0 gt if
    1
  else
    n 0 lt if -1 else 0 end
  end
end

(3) pnx export-c file.pnx [NAME] loads the file as pnx file.pnx does but runs none of its code, and writes the program’s code as C to standard output: a file that defines the table NAME (pnx_native when left out) for pinax_native_attach (19.11).