Skip to content

Lexical structure

This page describes how MaSH turns source text into words and symbols, before it works out what they mean.

A script is plain text. Line endings can be \n, \r\n or \r; all three are read as one line ending. Characters MaSH does not recognise, such as a byte-order mark or a zero-width space, are skipped without an error.

Spaces and tabs separate words and are otherwise ignored. Indentation has no meaning.

A comment starts with # and runs to the end of the line.

# a whole-line comment
set x to 5 # a trailing comment
printline x
5

A # inside a string is part of the string, not a comment.

printline "# not a comment"
printline 'also # not a comment'
# not a comment
also # not a comment

There are no block comments.

A name starts with a letter, an underscore, or any non-ASCII character. After that it can also contain digits. Accented letters work.

set café to 3
set _count2 to 4
printline café + _count2
7

Variable names are case sensitive. X and x are two variables.

set X to 1
set x to 2
printline X
printline x
1
2

Keywords are not case sensitive. IF, If and if are the same word, and so are PRINTLINE and printline.

set x to 2
IF x is 2
PRINTLINE "upper case works"
END
upper case works

These words belong to the language. You cannot use them as variable names, parameter names or block names, with two exceptions covered below.

asblockbreakbyclone
debugdecrementdivideeachelse
elseifendensureerrorerrorblock
exitfailfalsehereif
inincrementmatchesmultiplynext
notnullpblockraisereturn
setstoponerrorthrowtotrue
untilwhileyield

raise is another spelling of throw.

Some words only work as operators. They are written as words, but the reader turns them into symbols, so a variable cannot have these names either:

WordMeans
and, or&&, ||
otherwise??
thru, through...
is, equals, equal to==
is not!=
is less than, is more than, is greater than<, >

The Expressions page gives the full list.

else if (two words) is the same as elseif. here block (two words) is the same as pblock.

An assignment to a reserved word is refused when the script is compiled, and the script does not start. The names mash and event are refused too, because they always mean the built-in objects.

set mash to 1

Keywords after a dot, and as dictionary keys

Section titled “Keywords after a dot, and as dictionary keys”

After a dot, every word is a plain property name, whatever it is. In a dictionary literal, a reserved word works as a bare key.

set d to {if: 1, end: 2, Error: 3}
printline d.if
printline d.end
printline d["Error"]
1
2
3

null, true and false are literals. null is the absence of a value.

A number is a run of digits, with an optional decimal part. A leading - makes it negative. Underscores inside the digits are ignored, so large numbers can be read at a glance.

printline 1_000_000
printline .5
printline -3
printline 2.50
1000000
0.5
-3
2.5

There is no exponent form, no hexadecimal form and no octal form. 1e3 is read as the number 1 followed by the name e3. 0x10 is read as 0 followed by the name x10.

An integer too large for 64 bits becomes a decimal number.

printline 100000000000000000000
1.0E+20

A minus sign straight after a complete expression is a subtraction, with or without spaces.

block double n
return n * 2
end
set y to 3 -1
printline y
set z to 2 * 3 -1
printline z
set a to [5, 6]
printline a[1 -1]
printline (4 -1)
printline double(4 -1)
2
5
5
3
6

A string is text in double or single quotes. The two differ in two ways:

  • A double-quoted string has escape sequences and {{ }} interpolation.
  • A single-quoted string is literal. Only \\ and \' are escapes. Everything else, including \n and {{x}}, is kept as written.
set x to 5
printline "x is {{x}}"
printline 'x is {{x}}'
printline "two\nlines"
printline 'one\nline'
x is 5
x is {{x}}
two
lines
one\nline

A string can run across several lines. The line endings become part of the string.

printline "first
second"
first
second

The escape sequences in a double-quoted string are:

EscapeCharacter
\nnew line
\ttab
\rcarriage return
\b, \f, \v, \e, \abackspace, form feed, vertical tab, escape, bell
\0the zero character, or an octal code of up to two more digits after it
\xHHthe character with hexadecimal code HH
\\, \", \'a backslash, a double quote, a single quote
\{, \}a literal brace

A backslash before any other character is kept as it is, backslash included. "\d" is the two characters \d.

printline "tab[\t] quote[\"] backslash[\\] hex[\x41] other[\d]"
tab[ ] quote["] backslash[\] hex[A] other[\d]

In a double-quoted string, {{ expression }} is replaced by the value of the expression. The expression can be anything: a name, arithmetic, a method call, an index, even a string.

set n to 3
set d to {a: 7}
printline "n={{n}} next={{ n + 1 }} a={{ d['a'] }} items={{ count([1, 2]) }}"
n=3 next=4 a=7 items=2

Interpolation is shorthand for joining strings with ~. A null becomes an empty string, and a true or false becomes true or false.

set x to null
printline "[{{x}}] [{{true}}]"
[] [true]

Three rules cover the odd cases:

  • {{ with no }} before the closing quote is plain text.
  • {{{ makes the first brace literal.
  • \{{ stops interpolation.
set n to 3
printline "{{{n}}}"
printline "open {{ never closed"
printline "single {n}"
printline "escaped \{\{n}}"
{3}
open {{ never closed
single {n}
escaped {{n}}

A regular expression is written between slashes, with optional flags after the second slash: i, m, s, u, U and x. It is a string underneath, so it can be stored in a variable, and the matches operator accepts it.

set pattern to /a.c/i
printline "xABCx" matches pattern
printline "xyz" matches /a.c/
true
false

A / starts a regular expression only where an expression can start. After a number, a string, a name, ) or ], it is division. That makes a regular expression written straight after a bare name read as a division.

The library reference covers what you can do with patterns beyond matches.

TokenUse
+ - * / % **arithmetic
~join strings
== === != !==equality
< > <= >=comparison
&& || ! and, or, not
??first value that is not null
? :conditional expression; : also separates dictionary keys and named arguments
= += -= *= /=assignment
... ..< ..ranges
.member access
@reference to a block
=>recognised but not used by any statement
( ) [ ] { }grouping, calls, indexes, arrays, dictionaries, bodies
,separates items
;ends a statement, like a new line

A single & or | is an error: Unexpected character '&' — did you mean '&&'?

.. with two dots is a range, the same as ....

A new line ends a statement. A semicolon does the same, so several statements can share a line.

set x to 1; set y to 2; printline x + y
3

Blank lines are ignored.

A statement carries on to the next line in these cases:

  • The line ends with an operator, a comma, an opening bracket, =, . or =>.
  • The next line starts with . or =.
  • The statement is inside ( ), [ ], or an argument list. New lines are ignored there.
set total to 1 +
2 +
3
set list to [
10,
20
]
set text to "a"
printline total
printline count(list)
printline mash.collect([1, 2, 3])
.count()
6
2
3

Inside a { } body, new lines separate statements again, even if the braces are inside brackets.