Skip to content

Values and types

A value has a type. MaSH does not ask you to declare it, and a variable can hold any type at any time.

mash.type(value) returns the type as text. The types a script meets directly are:

TypeExamplemash.type()
nullnullNULL
booleantrueboolean
integer42integer
decimal2.5double
string"text"string
array[1, 2]array
dictionary{a: 1}dictionary
blockblock(x) { return x }block
objectmash.collect([1])the object’s own name
printline mash.type(5)
printline mash.type(2.5)
printline mash.type("s")
printline mash.type(null)
printline mash.type([1])
printline mash.type({a: 1})
integer
double
string
NULL
array
dictionary

null is the value of a variable that was never set, of a missing dictionary key read with d["key"], of an index past the end of an array, and of a statement that failed. Reading a name that does not exist gives null and is not an error.

printline mash.type(neverSet)
set d to {a: 1}
printline mash.type(d["missing"])
set list to [1, 2]
printline mash.type(list[5])
NULL
NULL
NULL

printline null prints an empty line. null inside a string is the empty string.

true and false. printline writes them as true and false. Joined into a string they also read true and false.

In an if, while, until, ensure, &&, ||, !, not or ? :, any value is read as a boolean. These are false:

  • false
  • null
  • the number 0 and 0.0
  • the empty string ""
  • the string "0"
  • an empty array [] and an empty dictionary {}

Everything else is true, including "false", "0.0" and " ".

set values to [0, "", "0", null, false, "no", 1, "0.0"]
each values as v
if v
print "true "
else
print "false "
end
end
printline ""
if []
printline "empty array is true"
else
printline "empty array is false"
end
false false false false false true true true
empty array is false

MaSH has integers and decimals. An integer is a whole number up to 64 bits. Arithmetic keeps whole results whole, and gives a decimal when the result is not whole.

printline 6 / 3
printline 7 / 2
printline mash.type(6 / 3)
printline mash.type(7 / 2)
printline 2 ** 3
printline 2 ** -1
printline 0.1 + 0.2
2
3.5
integer
double
8
0.5
0.3

Decimals print with up to 14 significant digits. They are floating point, so decimal fractions are approximate. Do not compare two computed decimals with == when exactness matters; use the money object for amounts of money.

Dividing or taking a remainder by zero does not stop the script. It records an error, and the result is NAN.

set x to 5 / 0
printline x
printline mash.error.message
NAN
Division by zero

% works on whole numbers. A decimal operand is cut to a whole number first: 7.5 % 2 is 1. The result takes the sign of the left side: -7 % 3 is -1.

A string that looks like a number is treated as one in arithmetic and numeric comparison. A string that does not look like a number is not.

printline "3" * "4"
printline 1 + "2"
printline "10" is less than "9"
printline "abc" is 0
12
3
false
false

+ never joins strings. "a" + "b" is an error, recorded and not printed. Use ~ to join.

A string is a sequence of characters. Lexical structure covers the quoting rules and escapes.

~ joins. A value of another type is converted to text first: numbers print as they do with printline, booleans become true or false, null becomes nothing, and an array or dictionary becomes JSON text.

printline "a" ~ 1 ~ true ~ null ~ 2.50
printline "list: " ~ [1, 2]
printline "dict: " ~ {a: 1}
a1true2.5
list: [1,2]
dict: {"a":1}

Two built-in blocks change case: uppercase(text) and lowercase(text).

printline uppercase("Hello")
printline lowercase("Hello")
HELLO
hello

A string takes the string methods and can be indexed. s.length is its length, s[0] is its first character and s[-1] its last.

set s to "abc"
printline s.upper()
printline s.length
printline s[0]
printline s[-1]
set s[0] to "z"
printline s
ABC
3
a
c
zbc

The library reference lists every string method. mash.str(text) gives the same methods and is still useful when you want a value that is explicitly a string object.

An array is an ordered list. Positions count from 0. Values can be of any type, and an array can hold other arrays.

set fruit to ["apple", "banana", "coconut"]
printline fruit[0]
printline fruit[2]
printline count(fruit)
apple
coconut
3

An index past the end gives null. A negative index is not counted from the end; it also gives null.

A range inside the brackets takes a slice, as a new array:

set a to [10, 20, 30, 40]
printline a[1...2]
printline a[..<2]
Array [
20,
30
]
Array [
10,
20
]

Set an element with an index, and add one at the end with []:

set a to ["x"]
a[] = "y"
set a[0] to "z"
printline a
Array [
"z",
"y"
]

count(value) gives the length of an array, a dictionary or a collection. It is an error on anything else, including null and a string.

Arrays nest, and you can read and write through any depth:

set x to [1, [2, 3], {a: [4]}]
printline x[1][0]
printline x[2].a[0]
x[2].a[] = 5
printline count(x[2].a)
2
4
2

An array that skips a position becomes a dictionary

Section titled “An array that skips a position becomes a dictionary”

An array has no gaps. Writing to an index that is not the next free position turns the array into a dictionary with number keys.

set a to ["a", "b"]
set a[5] to "c"
printline a
Dictionary {
"0": "a",
"1": "b",
"5": "c"
}

A dictionary maps keys to values and keeps them in the order they were added. A bare word before : is a text key. To use the value of an expression as a key, put it in brackets. Text and number keys are both allowed.

set name to "city"
set d to {first: "Ada", "last name": "Lovelace", (name): "London", 7: "seven"}
printline d.first
printline d["last name"]
printline d["city"]
printline d[7]
Ada
Lovelace
London
seven

Read with d.key or d["key"]. Write and add keys with either form. The two forms differ when the key is missing: d["key"] gives null, and d.key is an error.

set d to {a: 1}
set d.b to 2
set d["c"] to 3
printline count(d)
printline d["missing"]
3

Loop over a dictionary with each d as key, value. See Statements.

A plain dictionary takes the collection methods: d.has("a"), d.keys(), d.filter(...) and the rest. The methods that change a collection (remove, forget, put, push, prepend, add) write back when you call them on a variable or on a path such as d.a, and so does a chain of them (l.push(3).push(4)). A key named like a collection method (count, keys, first…) is still there: read it as d["count"], because d.count() calls the method.

set d to {a: 1, b: 2}
printline d.has("a")
d.remove("a")
printline d
d.put("c", 3)
printline d.keys()
true
Dictionary {
"b": 2
}
Array [
"b",
"c"
]

"a" in d looks for a value, not a key. To test for a key, use d.has("a").

set d to {a: 1}
printline d["missing"] ?? "none"
printline d.missing ?? "none"
printline "before"
printline d.missing
printline "after"
printline mash.error.message
none
none
before
after
Item "missing" does not exist.

Arrays and dictionaries are copied on assignment

Section titled “Arrays and dictionaries are copied on assignment”

Assigning an array or dictionary to another variable, or passing it to a block, makes a copy. Changing the copy does not change the original.

set a to [1, 2]
set b to a
b[] = 3
printline count(a)
printline count(b)
2
3

a...b is an array of the whole numbers from a to b, both included. a..<b leaves b out. A range with no start begins at 0. .. with two dots is the same as .... The ends can be any expression.

printline 1...4
printline 1..<4
set r to ...2
printline r
set n to 2
printline 1...n + 1
Array [
1,
2,
3,
4
]
Array [
1,
2,
3
]
Array [
0,
1,
2
]
Array [
1,
2,
3
]

A range counts down when the start is larger than the end.

printline 3...1
Array [
3,
2,
1
]

A range of single letters works as well: "a"..."e". Decimal ends step by 1 from the start: 1.5...4 is [1.5, 2.5, 3.5].

An exclusive range drops its end in either direction, and a range whose start and end are equal is empty. 5..<1 is 5 4 3 2, and 0..<0 is empty. A loop over 0..<count(list) therefore runs no passes for an empty list.

set list to []
each 0..<count(list) as i
print "range {{i}} "
end
printline "done"
printline 5..<1
printline count(0..<0)
done
Array [
5,
4,
3,
2
]
0

A regular expression literal is a string that starts and ends with /. See Lexical structure.

A block is a value. Define one inline with block, or refer to a named one with @name. You can pass it to something that takes a callback, such as the map method of a collection.

set double to block n { return n * 2 }
printline mash.collect([1, 2, 3]).map(double).toArray()
Array [
2,
4,
6
]

A block held in a variable can be passed on, but not called with name(...). Blocks and calls explains this.

Everything else is an object: a date, a collection, a file, an HTTP response. Objects come from mash. calls and have properties and methods. Unlike arrays and dictionaries, objects are shared: assigning one to a second variable gives two names for one object.

set a to mash.collect([1, 2])
set b to a
b.push(3)
printline a.count()
3

Dates are objects, and their add and sub methods change the date in place. Make a copy with .copy() first when you need to keep the original.

set start to mash.date("2026-03-15")
set later to start.copy().addDays(5)
printline start.format("Y-m-d")
printline later.format("Y-m-d")
2026-03-15
2026-03-20

clone(x) makes a deep copy of an array, a collection or a date, so changing the copy leaves the original alone. It refuses an object that holds a connection or other outside resource.

set a to [1, 2]
set b to clone(a)
b.push(3)
printline count(a)
printline count(b)
set d to mash.date("2026-03-15")
set e to clone(d)
e.addDays(5)
printline d.format("Y-m-d")
printline e.format("Y-m-d")
2
3
2026-03-15
2026-03-20

See Objects and member access for the rules every object follows.