8 Declarations

8.1 General

Syntax
declaration = [ "export" ] ( [ "bound" ] bindable | "shared" shareable | decl ) ;
bindable    = "global" NAME type
            | "array"  NAME type capacity
            | "buffer" NAME capacity ;
shareable   = bindable
            | "list"   NAME type capacity
            | "index"  NAME NAME PATH ;
decl        = "const"  NAME literal
            | "err"    NAME
            | "list"   NAME type capacity
            | "queue"  NAME type capacity
            | "pool"   NAME NAME capacity
            | "record" NAME { field | "big_endian" } "end"
            | "type"   NAME base
            | "index"  NAME NAME PATH
            | "tasks"  NAME capacity [ size [ size [ size ] ] ] [ "apart" ]
            | "extern" c-name "(" effect ")" "from" STRING [ "as" NAME ]
            | "test"   NAME { body } "end" ;
capacity    = size | "host" ;
size        = INT | NAME ;                         (* NAME: a const *)

(1) A declaration makes a name and what it stands for (a value, storage, a type). Declarations are written only outside definitions, at the outermost level of a source.

(2) A processor reads a source once, from top to bottom. A name shall be declared before the place where it is used. The exceptions are a word that calls itself, and forward (8.11).

(3) Within one module a name cannot be made twice, not even for different kinds of things. The names of core words and types cannot be made again either.

(4) A capacity (size) is a positive integer literal or a const of one. Capacities are fixed at load and do not change while running. All storage is placed inside the state’s block at load.

(4a) The capacity of an array, a buffer, a list, a queue, a pool or a task group may be written host. Then it is the number the host gave for that declaration before the load (19.4 (1d)), and the load treats it as that number written there. A load that meets host for a declaration the host gave no number is refused, and the message names the declaration. host is a word of the declaration only in that place; a constant of that name is not looked up there.

08-host-capacity.pnx

tasks handlers host 32 8192 apart     rem as many as the host serves at once
list seen int host
word count (gives n int) seen len end

Refused

$ pnx 08-host-capacity.pnx
08-host-capacity.pnx:1:16: the host gave no capacity for handlers (pinax_capacity)

(5) Globals, cells of arrays, slots of lists, queues and pools, buffers and fields of records start at zero (7 (3)).

Constraints

(6) Making a name that exists already is refused.

08-name-twice.pnx

global total int
const total 5

Refused

$ pnx 08-name-twice.pnx
08-name-twice.pnx:2:7: this name is already defined

(7) Using a name before its declaration is refused.

08-use-before.pnx

use str
word show (gives s str) total str.to_str end
global total int

Refused

$ pnx 08-use-before.pnx
08-use-before.pnx:2:25: this name is not declared before this place

8.2 Constants

(1) const NAME literal makes a constant. Its type is the literal’s. A constant has no storage: its value goes where it is used. Constants can be capacities, case values and row values.

Constraints

(2) Nothing can be stored into a constant. Inside a definition v to CONST would make a new local of the same name and is refused by the rule against hiding names (9.5); outside a definition to makes no local, so it is refused. set writes globals only (9.4).

08-const-store.pnx

const limit 3
word raise () 4 to limit end

Refused

$ pnx 08-const-store.pnx
08-const-store.pnx:2:20: this name is already visible here; a new name may not hide it

8.3 Globals

(1) global NAME type makes a global that holds one value. The type is int, bool, err, dec(N), str(N), a declared type, a row type (the name of a table) or a handle type; not str (7.3).

(2) A global is read by writing its name and stored with v set NAME (9.4). Its value stays as long as the state does — after a word’s run ends, and across later loads.

08-const-global.pnx

use io
use str
const limit 3
const greeting "hello"
global hits int
global last str(16)
global seen bool
hits str.to_str io.println           rem a global starts at zero
limit set hits
greeting set last
true set seen
cat (last, " ", hits, " ", seen) io.println

Output

$ pnx 08-const-global.pnx
0
hello 3 true

8.4 Arrays

Syntax
assign-item = "assign" NAME "(" value { "," value } ")" ;

(1) array NAME type capacity makes an array with that many cells. An array is always full: len is its capacity. The element type is int, bool, err, dec(N), str(N), a narrow integer (7.6), a declared type, the name of a record, or the name of a table, whose rows the cells then hold (8.8 (5a)). A cell of a narrow integer takes the bytes of its width, a cell of any other value 8 bytes; the cells of lists and queues are the same.

(2) i ARRAY idx reads cell i; v i set ARRAY stores into cell i; a cell of a collection parameter (9.9) is stored the same way. Numbers count from 0. For an array of records idx gives a row (8.8), not a value.

(3) assign ARRAY (value, …) fills an array from cell 0, outside definitions. With fewer values than cells the rest stay as they are. Assigning the same array again starts from cell 0 again. It may be used in a data load (13.4).

(4) The name of an array used as a stem yields its cells from 0 in order (10.6).

08-array.pnx

use io
use str
array primes int 6
assign primes (2, 3, 5, 7)                rem cells 4 and 5 stay 0
11 4 set primes                            rem v i to array: store into cell i
primes do (p int) p str.to_str io.print " " io.print end each
"" io.println
cat ("len ", primes len, ", cell 2 is ", 2 primes idx) io.println

Output

$ pnx 08-array.pnx
2 3 5 7 11 0 
len 6, cell 2 is 5
Constraints

(5) More values in assign than the array has cells is refused.

08-assign-many.pnx

array small int 2
assign small (1, 2, 3)

Refused

$ pnx 08-assign-many.pnx
08-assign-many.pnx:2:21: assign has more values than the array has cells
Faults

(6) An index below 0, or equal to or above the capacity, is the fault range.

08-array-range.pnx

use io
array primes int 4
"reading cell 4 of 4" io.println
4 primes idx drop

Fault

$ pnx 08-array-range.pnx
reading cell 4 of 4
08-array-range.pnx:4: index out of range

8.5 Lists

(1) list NAME type capacity makes a list whose length grows from 0 up to its capacity. Its cells are read and stored like an array’s, but only indexes below the length may be used.

(2) v LIST push adds one value at the end; LIST pop takes the last one off and gives it. Both can fail (or fail): a push into a full list fails with err.full, a pop from an empty list with err.range; a push of text too long for a list of str(N) fails with err.range (7.3 (4a)). A list of records gets an empty row at the end with LIST add_row, which gives that row. LIST clear sets the length to 0.

08-list.pnx

use io
use str
list stack int 3
word fill (or fail)
  1 stack push or_fail
  2 stack push or_fail
end
fill or_else (e) "fill failed" io.println end
cat ("len ", stack len, " of ", stack capacity) io.println
stack pop or_else (e) -1 end str.to_str io.println
cat ("len ", stack len) io.println

Output

$ pnx 08-list.pnx
len 2 of 3
2
len 1

08-list-full.pnx

use io
list two int 2
1 two push or_else (e) end
2 two push or_else (e) end
3 two push or_else (e) cat ("third push failed: ", e err_text) io.println end

Output

$ pnx 08-list-full.pnx
third push failed: full

(2a) n LIST truncate cuts the length to n, for lists of integers, of text and of records alike. It takes back a row added with add_row whose values turn out to be wrong.

08-list-truncate.pnx

use io
record city
  name str(16)
  people int
end
list cities city 4
rem Add a row, and take it back when its values turn out wrong.
word add_city (name str, people int or fail)
  cities len to before
  cities add_row or_fail to c
  name c to city.name
  people 0 lt if before cities truncate err.format fail end
  people c to city.people
end
"Seoul" 9411000 add_city or_else (e) end
"Nowhere" -1 add_city or_else (e) cat ("refused: ", e err_text) io.println end
cat (cities len, " rows") io.println

Output

$ pnx 08-list-truncate.pnx
refused: format
1 rows
Constraints

(2b) A list of records whose rows are kept elsewhere cannot be truncated. It is refused when the same source has a field, a global or a collection that holds its rows, a row of it named with row, or an index of it (8.10): once a new row takes the place cut off, they would point at the wrong row. A row held in a local is checked when it is used (fault range).

08-list-truncate-kept.pnx

record city
  name str(16)
end
list cities city 4
index by_name cities city.name
word forget () 0 cities truncate end

Refused

$ pnx 08-list-truncate-kept.pnx
08-list-truncate-kept.pnx:6:25: this list's rows are kept by a field, a global, a collection, a named row or an index; it cannot be truncated
Faults

(3) An index past the length is the fault range, even when it is below the capacity. So is a number for truncate larger than the length, or below zero.

08-list-past-len.pnx

list items int 8
1 items push or_else (e) end
1 items idx drop                          rem the list holds one value: index 1 is past it

Fault

$ pnx 08-list-past-len.pnx
08-list-past-len.pnx:3: index out of range
Diagnostics
ConditionMessage
truncate on a list whose rows are keptthis list's rows are kept by a field, a global, a collection, a named row or an index; it cannot be truncated

8.6 Queues

(1) queue NAME type capacity makes a queue: what goes in first comes out first. The element type is int, bool, err or dec(N). v QUEUE put puts a value at the back, QUEUE take takes the front one off and gives it, and QUEUE peek gives the front one without taking it. All three can fail: putting into a full queue is err.full, taking from an empty one err.range.

08-queue.pnx

use io
use str
queue jobs int 4
word show (gives s str or fail) jobs take or_fail str.to_str end
10 jobs put or_else (e) end
20 jobs put or_else (e) end
jobs peek or_else (e) 0 end str.to_str io.println
show or_else (e) "?" end io.println
show or_else (e) "?" end io.println
show or_else (e) cat ("empty: ", e err_text) end io.println

Output

$ pnx 08-queue.pnx
10
10
20
empty: range

8.7 Buffers

(1) buffer NAME capacity makes a buffer of that many bytes: a sequence of bytes that can be changed, for building text piece by piece.

(2) s BUF append, n BUF append_int (decimal; n an int or a narrow integer) and b BUF append_byte (b a u8) add at the end; when there is not enough room they fail with err.full and add nothing. BUF contents gives a str view of what it holds now. n BUF truncate cuts the length to n. BUF clear sets the length to 0.

08-buffer.pnx

use io
buffer out 32
word build (or fail)
  "x=" out append or_fail
  42 out append_int or_fail
  '!' out append_byte or_fail
end
build or_else (e) "full" io.println end
out contents io.println
2 out truncate
out contents io.println

Output

$ pnx 08-buffer.pnx
x=42!
x=

(3) The view contents gives points at the buffer’s bytes: when the buffer changes, so do the bytes the view sees.

8.8 Records and rows

Syntax
record   = "record" NAME { field | "big_endian" } "end" ;
field    = NAME width ;
width    = "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64"
         | "int" | "bool" | "varint" | "dec" "(" INT ")" | "str" "(" size ")"
         | NAME ;                                         (* NAME: a table *)
row-item = [ "export" ] "row" NAME [ NAME ] "(" value { "," value } ")" ;
value    = literal | NAME ;                         (* NAME: a const, a row name, none *)

(1) record NAME … end makes a record type, a sequence of fields, each with a width. An int or i64 field reads as int, a u8 … i32 field as that narrow integer (7.6), a bool field as bool, a dec(N) field as dec(N) (8 bytes), a str(N) field as a str view. A field takes a value of its type only. A field whose type is the name of a table holds a row of that table.

(2) Records are not handled as values. One declares an array, list or pool of records (a table) and handles its rows. A row’s type is named after its table. A row holds a number within the table, and every use checks its range (and, for a pool, its generation, 8.9).

(3) Fields are written as the path RECORD.FIELD: row RECORD.FIELD reads, v row to RECORD.FIELD stores.

(4) row TABLE (value, …) adds one row to a list, outside definitions. Values go in field order; trailing fields may be left out (they are zero). row TABLE NAME (…) names the row; the name stands for the row in later row values and in code.

08-record.pnx

use io
record user
  name str(16)
  age u8
  admin bool
end
list users user 8
row users ("kim", 30, true)
row users lee ("lee", 25)                  rem a named row; the fields left out stay zero
word show (u users) cat (u user.name, " ", u user.age, " ", u user.admin) io.println end
users do (u users) u show end each
26 u8 lee to user.age                         rem v row to record.field
lee show

Output

$ pnx 08-record.pnx
kim 30 true
lee 25 false
lee 26 false

(5) A field or global that holds a row starts as “none”. row is_none asks whether it is none, TABLE none gives the none row of that table, and row row_index gives the row’s number.

(5a) An array or a list whose element type is the name of a table holds rows of that table, in cells of 8 bytes: the row’s number plus one, with a pool row’s generation in the upper 32 bits; 0 is none, and every cell starts as none. Its cells are read, stored, pushed, popped and walked as any cells are, and a value going in shall be a row of that table. A row read from a cell is checked where it is used, as any row is. A queue holds no rows. A row that alloc has just given (8.9) is stored into a cell with set, which takes it over as a field does; push refuses it, since a push that fails would lose it.

08-row-cells.pnx

use io
record city
  name str(12)
  people int
end
list cities city 4
list big cities 4                          rem rows of cities
row cities ("Oslo", 709)
row cities ("Bergen", 291)
row cities ("Tromso", 78)
cities do (c cities gives bool) c city.people 100 gt end big filter_into or_else (e) end
big cities sort_by city.name
big do (c cities) c city.name io.println end each

Output

$ pnx 08-row-cells.pnx
Bergen
Oslo

(6) big_endian sets only the byte order that bytes.pack and bytes.unpack of 16.5 use. It does not change the layout in memory.

Constraints

(7) The values of row shall match the fields in order and type.

08-row-types.pnx

record user
  name str(16)
  age u8
end
list users user 8
row users (30, "kim")

Refused

$ pnx 08-row-types.pnx
08-row-types.pnx:6:12: a value has another type than its field
Faults

(8) An int stored into a narrow integer field is converted first (n u8); a value that does not fit the width makes that conversion the fault overflow (7.6). Nothing is cut: to store the low bits take a word such as low_u8. Reading or storing a field of the none row is the fault range.

08-record-narrow.pnx

record pixel
  level u8
end
array image pixel 4
word set_level (n int) n u8 0 image idx to pixel.level end     rem the conversion checks the range
300 set_level                                                   rem 300 does not fit in u8

Fault

$ pnx 08-record-narrow.pnx
08-record-narrow.pnx:5: integer overflow
  n int = 300

8.9 Pools

(1) pool NAME RECORD capacity makes a pool of rows that are taken and given back. POOL alloc takes a free row (or fail, err.full when none is left); row POOL free gives it back; row POOL is_alive asks whether the row is still alive.

(2) A row of a pool holds a generation besides its number. Giving a row back changes the generation of its slot, so a row used after it was given back is noticed — even when another row has taken the slot since.

(3) The row alloc gives is an owned value (12.4). On every path it shall be stored exactly once into a field or global, or given to free. Once stored, the generation check guards it.

08-pool.pnx

use io
record node
  value int
end
pool nodes node 2
global kept nodes
word demo (or fail)
  nodes alloc or_fail to a                 rem an owned row: it must be stored or freed
  7 a to node.value
  a set kept                                rem stored: the global holds the row now
  cat ("value ", kept node.value, ", alive: ", kept nodes is_alive) io.println
  kept nodes free
  cat ("alive after free: ", kept nodes is_alive) io.println
end
demo or_else (e) "no room" io.println end

Output

$ pnx 08-pool.pnx
value 7, alive: true
alive after free: false

(3a) POOL do (r POOL) … end each visits the rows of the pool that are alive, in the order of their slots, not in the order they were taken. A row given back in the block before the walk reaches it is not visited; a row taken in the block is visited when its slot comes later. each is the only combining word (10.6) that walks a pool.

Constraints

(4) A path on which a taken row is neither stored nor given back is refused.

08-pool-leak.pnx

record node
  value int
end
pool nodes node 2
word leak (or fail)
  nodes alloc or_fail to a
  7 a to node.value
end

Refused

$ pnx 08-pool-leak.pnx
08-pool-leak.pnx:8:1: an owned value in a local is not given on every path (close, free or store it)

(4a) A combining word other than each on a pool is refused.

08-pool-walk.pnx

record node
  value int
end
pool nodes node 4
word live (gives n int)
  nodes do (r nodes) true end count
end

Refused

$ pnx 08-pool-walk.pnx
08-pool-walk.pnx:6:22: a pool is walked by each only
Faults

(5) Using a row that was given back is the fault stale. pnx shows the locals of the word below the fault.

08-pool-stale.pnx

record node
  value int
end
pool nodes node 2
global keep nodes
word demo (or fail)
  nodes alloc or_fail to a
  a set keep                                 rem stored: the row is handed over
  keep nodes free
  keep node.value drop                      rem the row was given back: stale
end
demo or_else (e) end

Fault

$ pnx 08-pool-stale.pnx
08-pool-stale.pnx:10: a row or handle was used after it was given back
  a nodes = 4294967297
Diagnostics
ConditionMessage
A combining word other than each on a poola pool is walked by each only

8.10 Indexes

(1) index NAME TABLE RECORD.FIELD makes an index that finds rows of a table of records (an array, a list or a pool) by one field. The key field is int or str(N). An index is a hash table of fixed size, matched to the table’s capacity, that holds only row numbers; the data stay in the table.

(2) key INDEX lookup gives the row with that key (or fail, err.not_found when there is none). row INDEX insert puts a row in (or fail; it fails when the key is there already) and row INDEX remove takes it out. To change a key field, remove the row, change it and insert it again.

(2a) key INDEX has gives true when a row with that key is in the index, else false. It does not fail.

(3) Rows written with row at load are in the index. Rows added while running with add_row are found only after insert.

08-index.pnx

use io
use str
record user
  name str(16)
  age u8
end
list users user 8
index by_name users user.name
row users ("kim", 30)
row users ("lee", 25)
word find_age (name str gives age int or fail) name by_name lookup or_fail user.age raw end
"lee" find_age or_else (e) -1 end str.to_str io.println
"park" find_age or_else (e) -1 end str.to_str io.println

Output

$ pnx 08-index.pnx
25
-1

08-index-insert.pnx

use io
use str
record user
  name str(16)
  age int
end
list users user 4
index by_name users user.name
word add_user (name str, age int, indexed bool or fail)
  users add_row or_fail to r
  name r to user.name
  age r to user.age
  indexed if r by_name insert or_fail end  rem a row added while running is indexed by insert
end
word age_of (name str gives int or fail) name by_name lookup or_fail user.age end
"kim" 30 true add_user or_else (e) end
"lee" 25 false add_user or_else (e) end
"kim" age_of or_else (e) -1 end str.to_str io.println
"lee" age_of or_else (e) -1 end str.to_str io.println     rem not inserted: not found

Output

$ pnx 08-index-insert.pnx
30
-1

(3a) The table of an index may be a pool (8.9). Then lookup gives the row as it is now, with its generation; a row given to insert or remove after it was given back is the fault stale. free takes the row out of every index over its pool first. A row alloc has just given is handed over to insert (12.4): when insert fails, the row goes back to the pool before the failure passes on.

08-index-pool.pnx

use io
record user
  name str(16)
  age int
end
pool users user 4
index by_name users user.name
word add_user (name str, age int or fail)
  users alloc or_fail to r
  name r to user.name
  age r to user.age
  r by_name insert or_fail                rem hands the row over to the index
end
word forget (name str)
  name by_name lookup or_else (e) return end to r
  r users free                            rem out of by_name too
end
"kim" 30 add_user or_else (e) end
"lee" 25 add_user or_else (e) end
"kim" 41 add_user or_else (e) cat ("kim again: ", e err_text) io.println end
cat ("kim: ", "kim" by_name has, ", park: ", "park" by_name has) io.println
"kim" forget
cat ("kim after free: ", "kim" by_name has) io.println
users do (r users) cat (r user.name, " ", r user.age) io.println end each

Output

$ pnx 08-index-pool.pnx
kim again: failed
kim: true, park: false
kim after free: false
lee 25

(4) The hash is keyed (SipHash-2-4); the host gives the key (19.2). A host that indexes text from outside should give a random key: whoever knows the key can pile up collisions and make lookups slow.

Constraints

(5) An index over a pool is not shared (8.13), as a pool is not.

08-index-pool-shared.pnx

record user
  age int
end
pool users user 4
shared index by_age users user.age

Refused

$ pnx 08-index-pool-shared.pnx
08-index-pool-shared.pnx:5:21: an index over a pool is not shared, as a pool is not
Diagnostics
ConditionMessage
A table that is no array, list or pool of recordsan index belongs to a declared array, list or pool of records
shared before an index over a poolan index over a pool is not shared, as a pool is not
has on what is not an indexhas takes a key and the name of an index: key by_name has

8.11 Forward declarations

Syntax
forward = "forward" ( "array" | "list" | "pool" ) NAME
        | "forward" "word" NAME "(" effect ")" ;

(1) forward list NAME announces a table declared further on. It is for a record that holds rows of the table that holds it. The name of an announced table may be used only where a type goes. forward word is in 9.7.

08-forward.pnx

use io
use str
forward list nodes                         rem the record below points into this list
record node
  value int
  next nodes
end
list nodes node 4
row nodes b (2)
row nodes a (1, b)
a node.next node.value str.to_str io.println
a node.next node.next is_none if "the chain ends at b" io.println end

Output

$ pnx 08-forward.pnx
2
the chain ends at b
Constraints

(2) An announced table shall be declared in the same module with the same kind.

08-forward-missing.pnx

forward list nodes
record node
  value int
end

Refused

$ pnx 08-forward-missing.pnx
08-forward-missing.pnx:5:1: a table announced by forward is never declared

8.12 Bound storage

(1) global, array and buffer with bound in front get their storage from the host. Scripts use them like other globals. Until the host binds them they use room inside the state. 19.7 says how to bind.

8.13 Shared storage

(1) global, array, list, buffer and index with shared in front only read another state’s storage. The declaring state has no storage for them; the host gives it the same declaration of another state with pinax_share (19.7). Many states may read the same data at once, on many threads, with no copy and no lock.

Constraints

(2) Nothing changes a shared global: storing, set, push, pop, clear, truncate, add_row, storing into a field, insert, remove, the words that append to a buffer, and a map_into or filter_into destination are all refused. The processor also refuses, at load, bytecode that changes a shared global, and ends with the fault state bytecode that would change a shared global it was given as a parameter.

08-shared-write.pnx

record city
  name str(12)
  people int
end
shared list cities city 1000
word grow (r cities) r city.people 1 add r to city.people end

Refused

$ pnx 08-shared-write.pnx
08-shared-write.pnx:6:47: a shared global is read-only: nothing here may change it

(3) A shared collection may be passed as a collection parameter (9.9) of a word that only reads that parameter. A word changes its parameter when it uses an instruction that changes it, or passes it to a word that changes that parameter or to a host word’s buffer parameter. The processor decides once the whole source is read, so the same holds for a word announced with forward and for a word of an earlier load.

08-shared-param.pnx

use io
use str
shared list scores int 100
word total (l list int gives int) l sum end       rem reads its parameter only
word everyone (gives int) scores total end        rem so a shared list may be given to it
scores total str.to_str io.println                rem not given yet: empty

Output

$ pnx 08-shared-param.pnx
0

08-shared-change.pnx

shared list scores int 100
word reset (l list int) l clear end
word keep (l list int) l reset end                rem gives it to a word that changes it
word start () scores keep end

Refused

$ pnx 08-shared-change.pnx
08-shared-change.pnx:4:22: a shared collection is read-only: the word it is given to changes this parameter

(3a) A shared collection is not passed as a host word’s buffer parameter or as a parameter of the word a block value names (call), since what the receiver does is not known at load. redefine cannot make a word change a parameter it only read.

(4) shared does not stand before queue, pool, tasks, const or record.

08-shared-queue.pnx

shared queue jobs int 8

Refused

$ pnx 08-shared-queue.pnx
08-shared-queue.pnx:1:8: shared stands before global, array, list, buffer or index
Semantics

(5) Until the host gives it, it is empty: lists and arrays have length 0, an index finds nothing, a scalar is 0 and a buffer has no bytes. The code of a load runs as usual. Runs of the host are refused until every shared global is given (19.7).

8.14 Failure codes

(1) err NAME makes a failure code: a new error code (11.1), of type err. Like a constant (8.2) it has no storage, and its name is a name of its module: another module writes module.NAME, when it is exported. It is used where any code is: with fail, with or_else (e) and eq, as a case value, in an err global or collection. err_text gives its name.

(2) The failure codes a state declares are numbered from 0x400 in the order they are declared (19.1). A program does not depend on the number; a host reads the name (pinax_error_text).

08-err.pnx

use io
use str
err key_too_long
err value_too_long
word check (k str, v str or fail)
  k str.len 8 gt if key_too_long fail end
  v str.len 16 gt if value_too_long fail end
end
word try (k str, v str)
  k v check or_else (e)
    e case
      key_too_long of "the key is too long" end
      value_too_long of "the value is too long" end
      otherwise e err_text end
    end io.println
    return
  end
  "stored" io.println
end
"name" "value" try
"a long key name" "value" try
"name" "a value of thirty bytes, more" try
key_too_long err_text io.println

Output

$ pnx 08-err.pnx
stored
the key is too long
the value is too long
key_too_long
Constraints

(3) err is followed by a name, of at most 31 bytes. The name follows 8.1 (3) and (6).

08-err-name.pnx

err "too long"

Refused

$ pnx 08-err-name.pnx
08-err-name.pnx:1:5: err is followed by the name of a new failure code
Diagnostics
ConditionMessage
A name that exists alreadythis name is already defined
A name used before its declarationthis name is not declared before this place
A capacity host the host gave no number for (the declaration’s name in the message)the host gave no capacity for NAME (pinax_capacity)
to outside a definition into a name that is not a globaloutside a definition `to` makes no new local; a global is written with set
More assign values than cellsassign has more values than the array has cells
A queue of another element type than int, bool, err, dec(N)a queue holds int, bool, err or dec(N) values
push of a row alloc has just given (8.8 (5a))push can fail and lose an owned row; store it with set, into a field or into a global
A row value unlike its fielda value has another type than its field
A path that does not hand over an owned rowan owned value in a local is not given on every path (close, free or store it)
An announced table that is never declareda table announced by forward is never declared
A shared global is changeda shared global is read-only: nothing here may change it
A shared collection passed to a word that changes ita shared collection is read-only: the word it is given to changes this parameter
A shared collection passed to a block value’s worda shared collection is read-only: it is not given to the word a block value names, which may change it
redefine makes a read parameter changedredefine cannot make the word change a collection parameter the old one only read: shared collections may have been given to it
A shared buffer passed to a host worda shared buffer is read-only: it is not given to a host word, which may write it
Another declaration after sharedshared stands before global, array, list, buffer or index
err not followed by a nameerr is followed by the name of a new failure code
The name of a failure code over 31 bytesthe name of a failure code is at most 31 bytes long