Skip to main content

Data types

JARU is a dynamically typed language: the type belongs to the value, not to the variable. The same variable can hold an integer and, later on, a string, and you don't have to say beforehand which type it will have.

All values are first-class: they can be stored in variables, passed to functions and returned from them.

TypeDescriptionExample
boolTrue or false.true, false
nilThe absence of a value.nil
integer32-bit whole numbers.123, 0x1F
floatDouble-precision decimal numbers.0.456, 1e3
stringText strings, in single or double quotes.'Goku', "Krilin"
listAn ordered collection of elements that grows and shrinks.[1, 2, 3, 4]
mapA collection of key-value pairs.{"key1": "value1", "key2": "value2"}
arrayA fixed-size collection, with one or more dimensions.{1, 2, 3, 4}

Functions, classes, their instances and structs are values too: see Functions, Classes and objects and Structs.

Bool​

The boolean type represents a logical value: true or false. You can also write on and off: they are the same values (on == true is true).

var isAdult = true
var hasPermission = false

Booleans come out of comparisons and are used in control structures: if, while, for...

var age = 18
if (age >= 18) then
print("Is an adult")
else
print("Is a minor")
end

In the example, age is compared with 18: if it's greater than or equal, "Is an adult" is printed; otherwise, "Is a minor". Which other values count as true or false in an if is in What is false.

Nil​

nil is the absence of a value. A function that ends without return returns it, and you can assign it to say that a variable doesn't hold anything yet:

var target = nil
if (target == nil) then
println("No target")
end

A variable declared without a value isn't nil but 0: var x is the same as var x = 0.

nil has a single method:

MethodReturns
toString()The string "nil".

Any other method gives the error ERR_ATTRIBUTE_NOT_FOUND (Nil has no method 'size'.). It often happens when a function returns nil and you call a method on what it returned.

Integer​

Integers are whole numbers, 32-bit and signed: they go from -2147483648 to 2147483647.

var x = 10
var mask = 0xFF

A number written without a decimal point or exponent is an integer. If it doesn't fit in 32 bits, the compiler stores it as a float: 3000000000 is a float. Hexadecimal numbers, on the other hand, stay in 32 bits: 0xFFFFFFFF is the integer -1.

Overflow​

An integer addition, subtraction or multiplication that goes out of range doesn't raise an error: it wraps around and keeps the lower 32 bits.

var big = 2147483647
println(big + 1) // -2147483648
println(big * 2) // -2
var thousand = 100000
println(thousand * thousand) // 1410065408
println(thousand * 1.0 * thousand) // 10000000000

If a calculation can go beyond two billion, do it with floats: it's enough for one of the numbers to be a float, as in the last line.

Integer methods​

Methods are called on a variable, not on a written number: n.toHex() works and (255).toHex() doesn't compile (see Basic syntax).

MethodReturnsWith n = 255
isEven()true if it's even.false
isOdd()true if it's odd, negative numbers too.true
toBinary([digits])A string with the number in binary. digits, from 1 to 32, pads with zeros on the left."11111111"; with 12, "000011111111"
toHex([digits])A string with the number in hexadecimal, uppercase and without 0x. digits, from 1 to 8, pads with zeros."FF"; with 4, "00FF"
toBool()false if it's 0, and true otherwise.true
toByte()The lower 8 bits: an integer from 0 to 255.255 (300 gives 44 and -1 gives 255)
toFloat()The same number, as a float.255 (float)
toInt()The same integer.255
toString()A string with the number."255"
toUnsigned()Its unsigned value: a negative number becomes a float, with its 32 bits read as unsigned.255 (-1 gives 4294967295, a float)
  • If the number needs more digits than you ask for, it gets them all: n.toBinary(4) gives "11111111". Asking for more than 32 binary digits or 8 hexadecimal ones is the same as asking for the maximum.
  • With a negative number, toBinary() and toHex() show its 32 bits: -1 gives 32 ones and "FFFFFFFF".
  • If digits isn't an integer, the error is ERR_TYPE_MISMATCH; with more arguments than a method takes, ERR_INVALID_ARG_NUMBER.
var n = 255
println(n.isOdd()) // true
println(n.toHex()) // FF
println(n.toHex(4)) // 00FF
println(n.toBinary(12)) // 000011111111
var negative = -1
println(negative.toHex()) // FFFFFFFF
println(negative.toByte()) // 255
var big = 300
println(big.toByte()) // 44

toUnsigned() is for a 32-bit value with the top bit set, like 0xDEADBEEF read from a sensor: as an integer it comes out negative, and toUnsigned() gives its unsigned value. It doesn't fit in a JARU integer, so it comes out as a float, like a written number that doesn't fit in 32 bits:

var reading = 0xDEADBEEF
println(reading) // -559038737
println(reading.toUnsigned()) // 3735928559

Float​

Floats are double-precision floating-point numbers (64 bits, IEEE 754): they keep about 15 significant digits. A number written with a decimal point or an exponent is a float: 3.14, 1.0, 2.5e-3.

var x = 3.14

How they are printed​

println writes a float with just the digits needed so that, read back, it's the same number: 0.1 comes out as 0.1, and 0.1 + 0.2, which isn't exactly 0.3, comes out as 0.30000000000000004. toString() rounds it to 15 digits. A float without a fractional part is written without a point: 10.0 is written 10, although it's still a float.

The floats you write in the code don't lose digits, even when they're very small or very large: 1.23456789e-10 and 1e300 reach the program as they are. Only one smaller than 2.2e-308, not counting 0, becomes 0.

var total = 0.1 + 0.2
println(total) // 0.30000000000000004
println(total.toString()) // 0.3
var ten = 10.0
println(ten, " ", type(ten) == tFloat) // 10 true

Float methods​

MethodReturnsExample
toInt()The integer, dropping the decimals: it truncates toward zero.3.75 gives 3, and -3.75 gives -3
toBool()false if it's 0.0, and true otherwise.0.1 gives true
toFloat()The same float.3.75 gives 3.75
toString()A string with up to 15 significant digits.0.1 + 0.2 gives "0.3"
  • Outside the integer range, toInt(), like int(), keeps the lower 32 bits: 3000000000.0 gives -1294967296.
  • Floats don't have isEven(), toHex() or the other integer methods: calling them gives ERR_ATTRIBUTE_NOT_FOUND (Float has no method 'isEven'.). Convert them to an integer first with toInt().

NaN and infinity​

An operation without a numeric result, like sqrt(-1), gives NaN (not a number). And a result too big for a float gives infinity.

  • nan == nan is false, and nan != nan is true. To find out whether a value is NaN, compare it with itself: v != v is only true with NaN.
  • NaN is true in an if.
  • Lists and maps do find a NaN: a list that holds one gives true in contains().
  • Dividing by zero doesn't give infinity or NaN: it throws ERR_DIVISION_BY_ZERO, with floats too.
  • When printed, infinity comes out as inf. How NaN comes out depends on the platform: on Windows, -nan(ind).
var root = sqrt(-1)
println(root == root) // false
println(root != root) // true: it's NaN
var big = 1.0e100
var infinity = big * big * big * big
println(infinity) // inf

Integers, floats and division​

If there's a float in an operation, the result is a float: 1.5 + 2 gives 3.5. Between two integers, +, -, * and div give an integer, and / always gives a float, even when the division is exact: 6 / 3 is the float 2.

For an integer division, use div: it truncates toward zero, so 7 div 2 is 3 and -7 div 2 is -3. Use it especially when the result will be an index: a list doesn't accept a float index (ERR_TYPE_MISMATCH, List index must be an integer.), and a float doesn't have the integer methods.

println(7 / 2)       // 3.5
println(6 / 3) // 2 (it's a float)
println(7 div 2) // 3
println(-7 div 2) // -3
var list = [10, 20, 30, 40]
var middle = list.size() div 2
println(list[middle]) // 30

An integer and a float with the same value are equal: 1 == 1.0 is true. To convert from one type to another there are int(), float() and round(), in the Standard library, and the toInt() and toFloat() methods. All the operators are in Operators.

What changed in 0.9.7
  • / between two integers is calculated in double precision. It used to be done in single precision: 1 / 3 gave 0.3333333432674408 and int(80 / (40 / 30)) gave 59. Now they give 0.3333333333333333 and 60.
  • div between integers is exact over the whole 32-bit range; it used to fail from 16,777,216 on.
  • NaN is no longer equal to itself, and 0.0 == -0.0 is true.
  • On the ESP32-P4, int() and toInt() with a negative float gave 0. Now they truncate the same way on every platform.

String​

The string type holds text: a word, a sentence or a paragraph. It's written in double quotes or single quotes:

var name = "John"
var phrase = 'Hello world!'
var paragraph = "This is an example paragraph with multiple lines of text."

Strings have methods. For example, size() gives the length of a string and find() looks for some text inside it:

// Get the length of a character string
var name = "John"
println(name.size()) // Prints 4

// Search for a substring within a string
var phrase = "Hello world!"
println(phrase.find("world")) // Prints 6

All their methods, the quotes, the escape characters and the pitfalls are in Strings.

List​

A list holds a collection of values, of the same type or of different types. It's written with the values between square brackets [ ]:

var names = ["John", "Peter", "Michael"]
var ages = [25, 30, 35]
var mixed = ["John", 25, true]

Lists can be changed after they're created. You reach each element with its index between square brackets, starting at 0:

print(names[0]) // prints John
names[1] = "Andrew" // changes the value at position 1 to "Andrew"

Lists have methods for the usual operations. For example, append(value) adds an element at the end:

var names = ["John", "Peter"]
names.append("Michael") // the names list now contains ["John", "Peter", "Michael"]

All the list methods, foreach and how to copy them are in Lists, maps and arrays.

Map​

A map holds key-value pairs, like a dictionary. It's written with the pairs between braces {}:

var ages = {
"John": 25,
"Peter": 30,
"Michael": 35
}

You can also create an empty map and add the pairs later:

var ages = {}
ages["John"] = 25
ages["Peter"] = 30
ages["Michael"] = 35

You reach a value with its key between square brackets []:

var johnsAge = ages["John"] // johnsAge contains 25

Its methods (size(), keys(), values(), contains(key), remove(key)...) are in Lists, maps and arrays.

Array​

An array holds an ordered sequence of elements, like a list, but with a fixed size: once created, it doesn't grow or shrink.

It's written with the elements, separated by commas, between braces {}:

var numbers = {1, 2, 3, 4, 5}
var names = {"John", "Peter", "Michael"}

You can also create it empty, with its size, and assign the values later. Its elements start at 0:

var names[10]
names[8] = "This is a demo"

You reach an element with its index between square brackets [], starting at 0:

var numbers = {1, 2, 3, 4, 5}
var secondNumber = numbers[1] // secondNumber contains 2

Its methods (size(), numdim(), dim(), fill(value)...) are in Lists, maps and arrays.

Multidimensional arrays​

An array can have several dimensions: var table[10][20] creates an array of 10 × 20 elements. You reach them with one index per dimension, table[i][j]. It's a single array with two indexes, not an array of arrays: size() gives the total number of elements (200), and dim(), the size of each dimension ([10, 20]).

var array = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}

// Access a specific element in the matrix
println(array[1][1]) // Output: 5

// Iterate through matrix elements
for (var i = 0; i < 3; i++)
for (var j = 0; j < 3; j++)
println(array[i][j])
end
end

Examples of array declarations with different numbers of dimensions:

// One-dimensional array
var array1D = {1, 2, 3, 4, 5}

// Two-dimensional array
var array2D = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}

// Three-dimensional array
var array3D = {{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, {{10, 11, 12}, {13, 14, 15}, {16, 17, 18}}}

What is false​

In an if, in a while and with !, and and or, these values count as false:

  • false and nil;
  • the integer 0 and the float 0.0 (also -0.0);
  • the empty string "";
  • an empty list [], an empty map {} and an array of size 0.

Everything else is true: "0", "false", " ", [0], NaN, instances, structs, functions...

func describe(v)
if (v) then
return "true"
end
return "false"
end

println(describe(0)) // false
println(describe("")) // false
println(describe([])) // false
println(describe("0")) // true
println(describe([0])) // true

Finding the type: type()​

type(value) returns a number that tells you a value's type. Compare it with the type constants (tInteger, tFloat, tString, tList, tMap...), which are all in the Standard library.

func whatIs(v)
if (type(v) == tInteger) then
return "integer"
elsif (type(v) == tFloat) then
return "float"
elsif (type(v) == tString) then
return "string"
end
return "something else"
end

println(whatIs(7)) // integer
println(whatIs(7 / 2)) // float
println(whatIs("7")) // string
  • type is a reserved word, and it always takes parentheses.
  • An integer and a float have different types even when they have the same value: 1 == 1.0 is true, but type(1) == type(1.0) is false.
  • Functions and lambdas give tFunction. A method taken from an instance without calling it (var m = player.greet) gives tMethod. A class gives tClass, and its instances, tInstance.
  • Sprites, Bytes and other module objects give tAbstract.
  • The values of an enum are integers: they give tInteger.
  • There's no constant for structs: type() gives 23 for a struct and 24 for its declaration.
  • An error value, the one some functions return when they fail (see Errors and exceptions), gives tError, like the error of a catch.

All the known issues of JARU, together, are in Known issues.