7 Types and values

(1) Every value has a type fixed at load. Values do not carry their type while running, and no word asks for a type while running. A program whose types do not match is refused at load.

(2) The types are:

TypeValues
int64-bit two’s complement integers (7.1)
dec(N)decimals with N digits after the point, N from 0 to 18 (7.5)
u8 u16 u32 u64 i8 i16 i32unsigned and signed integers of that width (7.6); u8 is a byte, u32 a code point
booltrue, false (7.2)
stra view of bytes (7.3); only in locals, arguments and results
str(N)stored text of up to N bytes (7.3); in globals, cells of arrays, fields of records
errthe error code of a failure (11.1)
declared typea type made with type NAME base: the same representation as its base, a different type (7.4)
rowone row of a table of records (array, list, pool); the type is named after the table (8.8)
handlea number that refers to an object the host gave; the host names the type (19.6)
blocka word value, written word (signature) (10.5)
regexa regular expression program (16.7); visible only in a source that has use rex
buffera parameter type: it takes a buffer the script declared (8.7, 9.9, 19.3)

(3) Globals, cells of arrays and fields of records start at zero: int 0, bool false, str(N) the empty text, a row “none” (8.8).

7.1 Integers

(1) The values of int are the integers from −263 to 263−1, the same on every processor.

(2) The arithmetic words add sub mul div mod neg abs stop with the fault overflow when the result is out of range (11.3). There are words that wrap (wrap_add …), that stop at the edge (sat_add …), and that report overflow as a failure (chk_add …) (15.1).

(3) div rounds toward zero, and the result of mod has the sign of the dividend (a b div b mul a b mod add is a). floor_div and floor_mod round down. Division by zero is the fault div_zero.

07-int.pnx

use io
use str
9223372036854775807 str.to_str io.println          rem the largest int
7 2 div str.to_str io.println                       rem division rounds toward zero
-7 2 div str.to_str io.println
-7 2 mod str.to_str io.println                      rem the remainder has the sign of the dividend
-7 2 floor_div str.to_str io.println                rem floor_div rounds down
9223372036854775807 1 wrap_add str.to_str io.println    rem wrap_add wraps around
9223372036854775807 1 sat_add str.to_str io.println     rem sat_add stops at the edge

Output

$ pnx 07-int.pnx
9223372036854775807
3
-3
-1
-4
-9223372036854775808
9223372036854775807
Faults

(4) An overflow ends the run on the spot. In the example below the code after the line that overflows does not run.

07-int-overflow.pnx

use io
global big int
9223372036854775807 set big
"before" io.println
big 1 add set big
"never printed" io.println

Fault

$ pnx 07-int-overflow.pnx
before
07-int-overflow.pnx:5: integer overflow

7.2 Booleans

(1) bool has two values, true and false. Comparison words (eq lt …) give them; if, while, until and the logical words (and or not) take them.

07-bool.pnx

use io
use str
3 4 lt if "3 is less than 4" else "no" end io.println
true false or not if "never" else "true false or not is false" end io.println

Output

$ pnx 07-bool.pnx
3 is less than 4
true false or not is false
Constraints

(2) bool and int do not convert into each other. An int cannot stand where a condition goes, nor a bool where arithmetic goes.

07-bool-not-int.pnx

1 if 2 drop end

Refused

$ pnx 07-bool-not-int.pnx
07-bool-not-int.pnx:1:3: a bool is expected here

07-int-not-bool.pnx

true 1 add drop

Refused

$ pnx 07-int-not-bool.pnx
07-int-not-bool.pnx:1:8: this word takes two int values, or two of the same declared type

7.3 Strings

(1) A string is a sequence of bytes. There is no rule that it be UTF-8: words that work on bytes read bytes, and words that work on characters (str.char_count and so on) read UTF-8.

(2) There are two string types.

07-str.pnx

use io
use str
global name str(8)                 rem stored text: up to 8 bytes, copied in
word show (s str) cat ("[", s, "] ", s str.len) io.println end
"Pinax" set name
name show
"hello, world" 7 str.skip show        rem a view: no byte is copied

Output

$ pnx 07-str.pnx
[Pinax] 5
[world] 5
Constraints

(3) str for a global, an array or a field of a record is refused: the bytes a view points at may disappear before that place does.

07-str-global.pnx

global name str

Refused

$ pnx 07-str-global.pnx
07-str-global.pnx:2:1: stored text is str(N); a str view lives only in locals, arguments and results
Faults

(4) Putting text longer than N bytes into a str(N) place with no guard after the store stops with the fault range. It is not cut.

07-str-too-long.pnx

use io
global name str(4)
"four" set name
"more than four" set name

Fault

$ pnx 07-str-too-long.pnx
07-str-too-long.pnx:4: index out of range

(4a) A store into a str(N) place (a field with to, a global or a cell with set) may be followed by a guard (11.2). Then a text longer than N bytes is the failure range, and the place keeps what it held; a shorter text is stored and the guard does nothing. Under the guard of push onto a list of str(N) (8.5), a text too long is the failure range too, whether or not the list is full.

07-str-guard.pnx

use io
record entry
  name str(4)
end
list rows entry 2
list names str(4) 2

word keep (v str, r rows)
  v r to entry.name or_else (e) cat ("refused: ", v, " (", e err_text, ")") io.println return end
  cat ("stored: ", r entry.name) io.println
end

word main (or fail)
  rows add_row or_fail to r
  "four" r keep
  "fourteen" r keep
  cat ("still: ", r entry.name) io.println
  "fourteen" names push or_else (e) cat ("push: ", e err_text) io.println end
end
main or_else (e) e err_text io.println end

Output

$ pnx 07-str-guard.pnx
stored: four
refused: fourteen (range)
still: four
push: range

(4b) fits PLACE names a str(N) place as a store does: record.field, a global (also MODULE.NAME), or an array or a list, for its cells (also a collection parameter, 9.9). It takes the text on top of the stack and gives nothing; it writes nothing. A text of N bytes or fewer goes on; a longer one is the failure range. It is a word that can fail, so a guard follows it (11.2 (2)). A program asks a place with it before it does anything a text too long would leave half done, such as taking a row.

07-fits.pnx

use io
record entry
  name str(4)
end
list rows entry 2
array tags str(2) 2

word enter (v str)
  v fits entry.name or_else (e) cat ("refused: ", v, " (", e err_text, "), rows ", rows len) io.println return end
  rows add_row or_else (e) "full" io.println return end to r
  v r to entry.name
  cat ("entered: ", r entry.name, ", rows ", rows len) io.println
end

"four" enter
"fourteen" enter
"ab" fits tags or_else (e) "not reached" io.println end
"abc" fits tags or_else (e) cat ("tags: ", e err_text) io.println end

Output

$ pnx 07-fits.pnx
entered: four, rows 1
refused: fourteen (range), rows 1
tags: range

(5) A view of bytes made in the region cannot be used once the region is rewound past them; 12.3 states this rule.

7.4 Declared types

Syntax
type-decl = "type" NAME base ;
base      = "int" | "bool" | "str" | "dec" "(" INT ")" ;

(1) type meters int makes the declared type meters. Its values are represented like int and cost nothing more to run, but at load it is a different type. It keeps numbers of different units from being mixed.

(2) The name of the type written after a value of the base type makes a value of that type (1500 meters). The word raw turns a value of a declared type back into its base type.

(3) Two values of the same declared type can be added and subtracted with add and sub; the result has that type. Comparison words and min max also take two values of the same type. Other arithmetic is done after raw.

07-type.pnx

use io
use str
type meters int
type seconds int
word pace (d meters, t seconds gives int) d raw t raw div end     rem raw: the plain int
1500 meters 300 meters add 300 seconds pace str.to_str io.println

Output

$ pnx 07-type.pnx
6

(3a) A value of a declared type D over int multiplied by an int (D int mul, int D mul), divided by one or taken modulo one (D int div, D int mod) gives a D. neg and abs give a D too. Scaling a number up or down keeps its unit. The product and the quotient of two D values are not a D, so this rule does not cover them.

07-type-scale.pnx

use io
use str
type cents int
word share (total cents, people int gives cents) total people div end
word with_tax (c cents gives cents) c 110 mul 100 div end
1000 cents 3 share with_tax raw str.to_str io.println

Output

$ pnx 07-type-scale.pnx
366
Constraints

(4) A value of a declared type given where another type goes is refused. Below, the two arguments are swapped.

07-type-mixed.pnx

type meters int
type seconds int
word pace (d meters, t seconds gives int) d raw t raw div end
300 seconds 1500 meters pace drop          rem the two arguments are swapped

Refused

$ pnx 07-type-mixed.pnx
07-type-mixed.pnx:4:25: an argument has another type than the word's signature asks for

(5) Arithmetic words other than add and sub on two values of a declared type are refused. So is any arithmetic word outside (3a) on a declared type and an int.

07-type-arith.pnx

type meters int
2 meters 3 meters mul drop

Refused

$ pnx 07-type-arith.pnx
07-type-arith.pnx:2:19: a declared type is added to and subtracted from its own type, and multiplied, divided or taken modulo by an int; for other arithmetic take raw

7.5 Decimals

Syntax
dec-type = "dec" "(" INT ")" ;   (* 0 to 18 *)
DEC      = [ sign ] digits "." digits ;   (* no exponent: 6.4 *)

(1) A value of dec(N) is a decimal number with N digits after the point. It is held as an int scaled by 10N: 12.50 is a dec(2) held as 1250. Results are the same on every processor. The range is that of int divided by 10N.

(2) A number with a point and digits on both sides, without an exponent, is a dec(N) literal whose N is the count of digits after the point: 12.50 is dec(2), 0.5 is dec(1), -0.05 is dec(2).

(3) dec(N) is a number type over int as a declared type is (7.4): two values of the same dec(N) are added, subtracted, compared and given to min and max; a dec(N) is multiplied by an int, divided by one (rounded toward zero, as div) and taken modulo one; neg and abs keep it. In addition dec(A) dec(B) mul gives dec(A+B), exact, when A+B is at most 18. sum, minimum and maximum (10.6) take stems of dec(N) and give one. chk_add chk_sub chk_mul chk_div take a dec(N) as add sub mul div do. An overflow is the fault overflow.

(4) n dec(N) makes the int n into n with N zeros after the point (5 dec(2) is 5.00); d raw gives the scaled int (12.50 raw is 1250). d M rescale gives d as a dec(M), M a constant from 0 to 18: more digits are exact; fewer are rounded half away from zero (2.345 is 2.35, -2.345 is -2.35).

07-dec.pnx

use io
use str
type price dec(2)
word demo ()
  12.50 to unit                           rem dec(2): two digits after the point
  unit 3 mul to three                     rem times an int: still dec(2)
  three 0.75 add to bill
  cat ("bill ", bill, ", a quarter ", bill 4 div) io.println
  0.08 to rate
  bill rate mul to tax                    rem dec(2) times dec(2) is dec(4), exact
  cat ("tax ", tax, " -> ", tax 2 rescale) io.println
  cat (5 dec(2), " ", bill raw, " ", 9.99 price raw) io.println
  "19.9" 2 str.to_dec or_else (e) 0.00 end to read
  cat (read, " ", read bill gt) io.println
end
demo

Output

$ pnx 07-dec.pnx
bill 38.25, a quarter 9.56
tax 3.0600 -> 3.06
5.00 3825 9.99
19.90 false

07-dec-round.pnx

use io
word show (d dec(3)) cat (d, " -> ", d 2 rescale, ", ", d 0 rescale) io.println end
word demo ()
  2.344 show  2.345 show  -2.345 show  0.500 show  -0.500 show
end
demo

Output

$ pnx 07-dec-round.pnx
2.344 -> 2.34, 2
2.345 -> 2.35, 2
-2.345 -> -2.35, -2
0.500 -> 0.50, 1
-0.500 -> -0.50, -1

(5) type price dec(2) declares a type over dec(2); 9.99 price makes one and raw gives the dec(2). dec(N) is the type of globals, fields (8 bytes), cells of arrays and lists, values of queues and parameters (9.9) as int is. str.to_str and cat write it with all its digits (16.1); str.to_dec reads one.

Constraints

(6) Two dec values of different scales are not added or compared; rescale one first. A dec is not divided by a dec.

07-dec-scales.pnx

word total (gives dec(2)) 1.50 0.5 add end      rem dec(2) and dec(1)

Refused

$ pnx 07-dec-scales.pnx
07-dec-scales.pnx:1:36: two dec values of different scales: rescale one first (d 2 rescale)

07-dec-div.pnx

word share (gives dec(2)) 10.00 3.00 div end

Refused

$ pnx 07-dec-div.pnx
07-dec-div.pnx:1:38: a dec is divided by an int, not by a dec; for a share, rescale first and divide by the int

7.6 Narrow integers

(1) u8 u16 u32 u64 i8 i16 i32 are the narrow integer types. The name says the range: u from 0 to 2width−1, i from −2width−1 to 2width−1−1. u8 is the type of a byte and u32 the type of a code point. The 64-bit signed integer is int; there is no type i64.

(2) A narrow integer is another type than int, and each is another type than the others. Conversion is a word: n u8 makes the int or other narrow integer n into a u8, and a value outside the range is the fault overflow. Nothing is truncated. A literal is written the same way: 255 u8. A constant outside the range is refused at load. x raw gives the int held; raw of a u64 puts its 64 bits into the int as they are (263 and above are negative). n low_u8 … n low_i32 take the low bits only: these are the truncating words.

(3) add sub mul div mod take two of one narrow integer type and give that type; a result outside the range is the fault overflow. wrap_add wrap_sub wrap_mul wrap modulo 2width, sat_add sat_sub sat_mul stop at the ends of the range, and chk_add chk_sub chk_mul chk_div fail. The comparison words take two of one type; u64 compares and divides unsigned. neg abs min max clamp do not take them (15.1).

(4) The bit words work within the width. bit_not flips the bits of the width only, shl drops the bits that leave the width, and shr keeps the sign of a signed type and fills an unsigned one with zeros. u_shr u_div u_mod u_lt u_le u_gt u_ge floor_div floor_mod are, on an unsigned type, shr div mod lt le gt ge div mod, and do not take a signed type. count_ones leading_zeros trailing_zeros count the bits of the width and give an int (the width for 0); byte_swap reverses the bytes of the width. rotl rotr mul_hi u_mul_hi take u64 only.

(5) A narrow integer is, as int is, the type of globals, of cells of arrays and lists, of values of queues, of parameters and results and of host word signatures (19.2), and the base of a declared type (7.4), as in type opcode
u8
. A cell of an array, list or queue and a field of a record take the bytes of the width (array a u8 1024 is 1024 bytes); one global is an 8-byte place. A character literal is a u8 or a u32 (6.5). case compares a narrow integer (10.3). A host word takes and gives the value as one 64-bit integer, and a result outside the range is the fault overflow (19.2).

07-narrow.pnx

use io
use str
word show (n int) n str.to_str io.println end
word demo ()
  250 u8 5 u8 add raw show               rem 255
  0 u8 1 u8 wrap_sub raw show            rem 255: wraps at 2^8
  200 u8 100 u8 sat_add raw show         rem 255: stops at the end
  200 u8 bit_not raw show                rem 55: eight bits flipped
  -1 low_u8 raw show                     rem 255: the low byte of -1
  0 u64 bit_not 1 u64 gt if "u64 compares unsigned" io.println end
  0 u64 bit_not raw show                 rem -1: the bits as an int
end
demo

Output

$ pnx 07-narrow.pnx
255
255
255
55
255
u64 compares unsigned
-1
Constraints

(6) An int with a narrow integer, or two narrow integers of different types, given to one word is refused. A constant outside the range converted is refused.

07-narrow-mix.pnx

word f (gives u8) 1 u8 1 add end

Refused

$ pnx 07-narrow-mix.pnx
07-narrow-mix.pnx:1:26: this word takes two values of one type: u8 with u8, int with int; u8 and raw convert

07-narrow-range.pnx

word f (gives u8) 256 u8 end

Refused

$ pnx 07-narrow-range.pnx
07-narrow-range.pnx:1:23: the constant is outside the range of this type; low_u8 and the others keep the low bits
Faults

(7) A result or a conversion outside the range ends the run with the fault overflow.

07-narrow-overflow.pnx

use io
word byte (n int gives u8) n u8 end
"before" io.println
255 byte 1 u8 add drop
"never printed" io.println

Fault

$ pnx 07-narrow-overflow.pnx
before
07-narrow-overflow.pnx:4: integer overflow
Diagnostics
ConditionMessage
A value that is not a bool where a condition goesa bool is expected here
A narrow integer with an int, or two narrow integers of different typesthis word takes two values of one type: u8 with u8, int with int; u8 and raw convert
A constant outside the range convertedthe constant is outside the range of this type; low_u8 and the others keep the low bits
A value of a declared type converted directlyu8, u16, u32, u64, i8, i16, i32 and low_u8 ... are made from an int or from one another; for a declared type take raw first
u_shr on a signed narrow integeru_shr takes an int or an unsigned type; a signed type shifts with shr
u_div u_mod floor_div floor_mod on a signed narrow integeru_div, u_mod, floor_div and floor_mod take an int or an unsigned type
u_lt u_le u_gt u_ge on a signed narrow integeru_lt, u_le, u_gt and u_ge take an int or an unsigned type; a signed type compares with lt
rotl rotr on a narrow integer other than u64rotl and rotr take an int or a u64; for a narrow type take raw
mul_hi u_mul_hi on a narrow integer other than u64mul_hi and u_mul_hi take an int or a u64; for a narrow type take raw
abs on a narrow integerabs takes an int or a declared type over int; a narrow type has no abs
The two values of an arithmetic word differ in type or are not intthis word takes two int values, or two of the same declared type
Arithmetic outside (3) and (3a) on a declared typea declared type is added to and subtracted from its own type, and multiplied, divided or taken modulo by an int; for other arithmetic take raw
An argument of another type than the signaturean argument has another type than the word's signature asks for
str for a global and the likestored text is str(N); a str view lives only in locals, arguments and results
str for a field of a recorda field holds str(N); a str view lives only in locals, arguments and results
Two dec of different scalestwo dec values of different scales: rescale one first (d 2 rescale)
A dec divided by a deca dec is divided by an int, not by a dec; for a share, rescale first and divide by the int
dec(A) dec(B) mul beyond 18 digitsdec(A) times dec(B) is dec(A+B), and A+B is at most 18; rescale one first
A scale outside 0 to 18dec(N) has 0 to 18 digits after the point
fits before a place that is not str(N)fits names a place of type str(N): record.field, a global, or an array or a list
fits before a localfits names a place a store keeps; a local holds a text of any length
fits before a name that is not declaredfits names a declared global, array or list; there is none of this name
fits with a value that is not strfits takes a text: text fits place
fits with no place after itfits is followed by a place: text fits record.field, or the name of a global