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:
| Type | Values |
int | 64-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 i32 | unsigned and signed integers of that width (7.6); u8 is a byte, u32 a code point |
bool | true, false (7.2) |
str | a 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 |
err | the error code of a failure (11.1) |
| declared type | a type made with type NAME base: the same representation as its base, a different type (7.4) |
| row | one row of a table of records (array, list, pool); the type is named after the table (8.8) |
| handle | a number that refers to an object the host gave; the host names the type (19.6) |
| block | a word value, written word (signature) (10.5) |
regex | a regular expression program (16.7); visible only in a source that has use rex |
buffer | a 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.
stris a view: the start and length of bytes that are somewhere (a literal, the text of a global, a buffer, the region). Making one copies no bytes. It lives only in locals, arguments, results and on the stack.str(N)is stored text. It takes N bytes of room and copies the bytes put into it. It is used only in globals, cells of arrays and lists, and fields of records. Reading it gives astrview of that room.
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. A cell of an array, list or queue and a field of a record take the bytes of the width (
u8array 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
| Condition | Message |
|---|---|
A value that is not a bool where a condition goes | a bool is expected here |
A narrow integer with an int, or two narrow integers of different types | this word takes two values of one type: u8 with u8, int with int; u8 and raw convert |
| A constant outside the range converted | the constant is outside the range of this type; low_u8 and the others keep the low bits |
| A value of a declared type converted directly | u8, 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 integer | u_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 integer | u_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 integer | u_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 u64 | rotl 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 u64 | mul_hi and u_mul_hi take an int or a u64; for a narrow type take raw |
abs on a narrow integer | abs 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 int | this word takes two int values, or two of the same declared type |
| Arithmetic outside (3) and (3a) on a declared type | 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 |
| An argument of another type than the signature | an argument has another type than the word's signature asks for |
str for a global and the like | stored text is str(N); a str view lives only in locals, arguments and results |
str for a field of a record | a field holds str(N); a str view lives only in locals, arguments and results |
Two dec of different scales | two dec values of different scales: rescale one first (d 2 rescale) |
A dec divided by a dec | a 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 digits | dec(A) times dec(B) is dec(A+B), and A+B is at most 18; rescale one first |
| A scale outside 0 to 18 | dec(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 local | fits names a place a store keeps; a local holds a text of any length |
fits before a name that is not declared | fits names a declared global, array or list; there is none of this name |
fits with a value that is not str | fits takes a text: text fits place |
fits with no place after it | fits is followed by a place: text fits record.field, or the name of a global |