convert - explicit type conversion
Enable with use convert;. Provides one-argument conversion functions plus typeOf for runtime kind introspection. Every function either returns the converted value or produces a positioned runtime error.
use io;
use convert;
def n as int init convert.toInt("42"); # parse string -> 42
def f as float init convert.toFloat(5); # int -> 5.0
def s as string init convert.toString(3.14); # any -> "3.14"
def b as bool init convert.toBool(0); # 0 -> false
io.printf("%s\n", convert.typeOf(5 / 2)); # "float" (after Python 3 / change)
io.printf("%s\n", convert.typeOf(5 // 2)); # "int"Behavior summary
| Call | Source kinds | Behavior |
|---|---|---|
convert.toInt(v) | int / float / string / bool | identity / truncate / parse / true=1, false=0 |
convert.toFloat(v) | int / float / string / bool | convert / identity / parse / true=1.0, false=0.0 |
convert.toString(v) | any | always succeeds; uses the value's display form |
convert.toBool(v) | bool / int / float / string | identity / canonical only (0/1, 0.0/1.0, "true"/"false") |
convert.typeOf(v) | any | returns the kind as a string: "int", "float", ..., "list", "map", "object" |
convert.objectType(v) | object | specific registered name of an opaque object, e.g. "json.Value"; errors on a non-object |
convert.bytesFromString(s, codec) | (string, string) | string to bytes; "utf-8" only (other codecs live in encoding) |
convert.stringFromBytes(b, codec) | (bytes, string) | bytes to string; "utf-8" only; invalid UTF-8 is an error |
convert.toCodepoint(char) | string | Unicode code point (int) of a one-rune string; errors unless the argument is exactly one code point |
convert.fromCodepoint(n) | int | the one-rune string for code point n; errors on a negative / out-of-range / surrogate value |
bytesFromString / stringFromBytes are the UTF-8 cross-kind pair (string to bytes and back) - the one codec every program needs. Every other character encoding (ISO-8859-, Windows-, EBCDIC) and the binary-to-text codecs (hex, base64, quoted-printable) live in encoding: encoding.encode / decode and encoding.toText / fromText.
toCodepoint / fromCodepoint: code point, not "character"
These convert between a single code point (a Unicode scalar value, an int) and the one-rune string that holds it - the primitive a Unicode algorithm needs (Punycode digits, a \x01 control byte, an escape). Two things worth being precise about:
- The whole range works, not just ASCII or the BMP.
fromCodepointaccepts any scalar value0..0x10FFFF(hex literals are fine:fromCodepoint(0x20AC)is€,fromCodepoint(0x1F602)is😂). The result is one rune whose UTF-8 encoding is 1 to 4 bytes - a code point is not a byte. Only a negative value, one above0x10FFFF, or a UTF-16 surrogate (0xD800..0xDFFF) errors. - "One rune" is not "one character a reader sees." A user-perceived character - a grapheme cluster - can be several code points: a base plus combining marks (
e+ U+0301 =é), an NFD-decomposed accent, an emoji ZWJ sequence (👨👩👧). Each of those code points round-trips individually, buttoCodepointtakes exactly one code point, so it rejects a multi-rune cluster the same way it rejects"ab".len,strings.chars, andstrings.substringare all rune-indexed too (see strings.md); Jennifer has no grapheme-cluster API. The tell: precomposedé(U+00E9) islen1 andtoCodepointgives 233; decomposedé(e+ U+0301) islen2 andtoCodepointerrors.
Errors
convert.toInt("abc")- parse failure (string doesn't represent a valid integer).convert.toInt(null)- no conversion defined.convert.toInt(f)for a NaN, +/-Infinity, or out-of-int64-range float - the value has no representable integer (truncation would be garbage).convert.toBool("maybe")- strings: only"true"and"false"accepted.convert.toBool(123),convert.toBool(-1)- ints: only0and1accepted.convert.toBool(1.5)- floats: only0.0and1.0accepted.convert.stringFromBytes(b, "utf-8")on bytes that aren't valid UTF-8 - strict at boundaries; no silent replacement characters.convert.bytesFromString(s, "iso-8859-1")or any non-"utf-8"codec name - rejected as unsupported; useencoding.encode/decodefor those.- Arity errors (too many or too few arguments).
For "any nonzero counts as true" semantics, write the comparison explicitly:
def b as bool init $count != 0; // not convert.toBool($count)This matches the strict-conditions rule everywhere else in Jennifer - if you want to project an arbitrary value into a bool, state the criterion.
Notes on the verb naming
The convert library's four conversion callees are named toInt, toFloat, toString, toBool (not int / float / string / bool) so they don't collide with the type keywords - the parser keeps those reserved for declarations (def x as int ...). The to-prefixed verb also reads as English at the call site: convert.toInt("42") says "convert to int." typeOf is a normal identifier and is not a type keyword.
Writing the bare form (int("42"), string(42), ...) is a parse error directing you at the convert.to* form.
Objects: typeOf vs objectType
Some libraries hand back an opaque object - a value that carries data but exposes it only through that library's own accessors, not through operators or [index] / .field. The first is json.Value (from json.decode). For any such value convert.typeOf returns the generic "object", and convert.objectType returns the specific registered name so you can tell one object family from another:
def doc as json.Value init json.decode("{}");
io.printf("%s\n", convert.typeOf($doc)); # object
io.printf("%s\n", convert.objectType($doc)); # json.Valueconvert.objectType errors on a non-object, so guard with convert.typeOf(v) == "object" first if the kind is unknown.
See also: encoding.md (all other character and binary-to-text codecs), ../user-guide/index.md, ../technical/interpreter.md, index.md.