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:
| Type | Example | mash.type() |
|---|---|---|
| null | null | NULL |
| boolean | true | boolean |
| integer | 42 | integer |
| decimal | 2.5 | double |
| string | "text" | string |
| array | [1, 2] | array |
| dictionary | {a: 1} | dictionary |
| block | block(x) { return x } | block |
| object | mash.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})integerdoublestringNULLarraydictionaryprintline(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}))integerdoublestringNULLarraydictionarynull 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])NULLNULLNULLprintline(mash.type(neverSet))d = {a: 1}printline(mash.type(d["missing"]))list = [1, 2]printline(mash.type(list[5]))NULLNULLNULLprintline null prints an empty line. null inside a string is the empty string.
Booleans
Section titled “Booleans”true and false. printline writes them as true and false. Joined into a string they also read true and false.
What counts as true
Section titled “What counts as true”In an if, while, until, ensure, &&, ||, !, not or ? :, any value is read as a boolean. These are false:
falsenull- the number
0and0.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 " endendprintline ""if [] printline "empty array is true"else printline "empty array is false"endfalse false false false false true true trueempty array is falsevalues = [0, "", "0", null, false, "no", 1, "0.0"]each (values) as v { if (v) { print("true ") } else { print("false ") }}printline("")if ([]) { printline("empty array is true")} else { printline("empty array is false")}false false false false false true true trueempty array is falseNumbers
Section titled “Numbers”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 / 3printline 7 / 2printline mash.type(6 / 3)printline mash.type(7 / 2)printline 2 ** 3printline 2 ** -1printline 0.1 + 0.223.5integerdouble80.50.3printline(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)23.5integerdouble80.50.3Decimals 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 / 0printline xprintline mash.error.messageNANDivision by zerox = 5 / 0printline(x)printline(mash.error.message)NANDivision 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.
Numbers in strings
Section titled “Numbers in strings”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 0123falsefalseprintline("3" * "4")printline(1 + "2")printline("10" < "9")printline("abc" == 0)123falsefalse+ never joins strings. "a" + "b" is an error, recorded and not printed. Use ~ to join.
Strings
Section titled “Strings”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.50printline "list: " ~ [1, 2]printline "dict: " ~ {a: 1}a1true2.5list: [1,2]dict: {"a":1}printline("a" ~ 1 ~ true ~ null ~ 2.50)printline("list: " ~ [1, 2])printline("dict: " ~ {a: 1})a1true2.5list: [1,2]dict: {"a":1}Two built-in blocks change case: uppercase(text) and lowercase(text).
printline uppercase("Hello")printline lowercase("Hello")HELLOhelloprintline(uppercase("Hello"))printline(lowercase("Hello"))HELLOhelloA 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.lengthprintline s[0]printline s[-1]set s[0] to "z"printline sABC3aczbcs = "abc"printline(s.upper())printline(s.length)printline(s[0])printline(s[-1])s[0] = "z"printline(s)ABC3aczbcThe 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.
Arrays
Section titled “Arrays”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)applecoconut3fruit = ["apple", "banana", "coconut"]printline(fruit[0])printline(fruit[2])printline(count(fruit))applecoconut3An 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]a = [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 aArray [ "z", "y"]a = ["x"]a[] = "y"a[0] = "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[] = 5printline count(x[2].a)242x = [1, [2, 3], {a: [4]}]printline(x[1][0])printline(x[2].a[0])x[2].a[] = 5printline(count(x[2].a))242An 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 aDictionary { "0": "a", "1": "b", "5": "c"}a = ["a", "b"]a[5] = "c"printline(a)Dictionary { "0": "a", "1": "b", "5": "c"}Dictionaries
Section titled “Dictionaries”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.firstprintline d["last name"]printline d["city"]printline d[7]AdaLovelaceLondonsevenname = "city"d = {first: "Ada", "last name": "Lovelace", (name): "London", 7: "seven"}printline(d.first)printline(d["last name"])printline(d["city"])printline(d[7])AdaLovelaceLondonsevenRead 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 2set d["c"] to 3printline count(d)printline d["missing"]3d = {a: 1}d.b = 2d["c"] = 3printline(count(d))printline(d["missing"])3Loop 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 dd.put("c", 3)printline d.keys()trueDictionary { "b": 2}Array [ "b", "c"]d = {a: 1, b: 2}printline(d.has("a"))d.remove("a")printline(d)d.put("c", 3)printline(d.keys())trueDictionary { "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.missingprintline "after"printline mash.error.messagenonenonebeforeafterItem "missing" does not exist.d = {a: 1}printline(d["missing"] ?? "none")printline(d.missing ?? "none")printline("before")printline(d.missing)printline("after")printline(mash.error.message)nonenonebeforeafterItem "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 ab[] = 3printline count(a)printline count(b)23a = [1, 2]b = ab[] = 3printline(count(a))printline(count(b))23Ranges
Section titled “Ranges”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...4printline 1..<4set r to ...2printline rset n to 2printline 1...n + 1Array [ 1, 2, 3, 4]Array [ 1, 2, 3]Array [ 0, 1, 2]Array [ 1, 2, 3]printline(1...4)printline(1..<4)r = ...2printline(r)n = 2printline(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...1Array [ 3, 2, 1]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}} "endprintline "done"printline 5..<1printline count(0..<0)doneArray [ 5, 4, 3, 2]0list = []each (0..<count(list)) as i { print("range {{i}} ")}printline("done")printline(5..<1)printline(count(0..<0))doneArray [ 5, 4, 3, 2]0Regular expressions
Section titled “Regular expressions”A regular expression literal is a string that starts and ends with /. See Lexical structure.
Blocks as values
Section titled “Blocks as values”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]double = 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.
Objects
Section titled “Objects”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 ab.push(3)printline a.count()3a = mash.collect([1, 2])b = ab.push(3)printline(a.count())3Dates 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-152026-03-20start = mash.date("2026-03-15")later = start.copy().addDays(5)printline(start.format("Y-m-d"))printline(later.format("Y-m-d"))2026-03-152026-03-20clone(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")232026-03-152026-03-20a = [1, 2]b = clone(a)b.push(3)printline(count(a))printline(count(b))d = mash.date("2026-03-15")e = clone(d)e.addDays(5)printline(d.format("Y-m-d"))printline(e.format("Y-m-d"))232026-03-152026-03-20See Objects and member access for the rules every object follows.