Scripting Language

An AutoHotkey script is basically a set of instructions for the program to follow, written in a custom language exclusive to AutoHotkey. This language bears some similarities to several other scripting languages, but also has its own unique strengths and pitfalls. This document describes the language and also tries to point out common pitfalls.

See Concepts and Conventions for more general explanation of various concepts utilised by AutoHotkey.

There are two distinct styles of syntax used in AutoHotkey: legacy syntax and expressions.

Table of Contents

General Conventions

Names: Variable and function names are not case-sensitive (for example, CurrentDate is the same as currentdate). For details such as maximum length and usable characters, see Names.

No typed variables: Variables have no explicitly defined type; instead, a value of any type can be stored in any variable (excluding built-in variables). Numbers may be automatically converted to strings (text) and vice versa, depending on the situation.

Declarations are optional: Except where noted on the functions page, variables do not need to be declared; they come into existence simply by using them (and each variable starts off empty/blank).

Spaces are mostly ignored: Indentation (leading space) is important for writing readable code, but is not required by the program and is generally ignored. Spaces and tabs are generally ignored at the end of a line, within an expression (except between quotes), and before and after command parameters. However, spaces are significant in some cases, including:

Line breaks are meaningful: Line breaks generally act as a statement separator, terminating the previous command or expression. (A statement is simply the smallest standalone element of the language that expresses some action to be carried out.) The exception to this is line continuation (see below).

Line continuation: Long lines can be divided up into a collection of smaller ones to improve readability and maintainability. This is achieved by preprocessing, so is not part of the language as such. There are two methods:

Comments

Comments are portions of text within the script which are ignored by the program. They are typically used to add explanation or disable parts of the code.

Scripts can be commented by using a semicolon at the beginning of a line. For example:

; This entire line is a comment.

Comments may also be added at the end of a line, in which case the semicolon must have at least one space or tab to its left. For example:

Run Notepad  ; This is a comment on the same line as a command.

In addition, the /* and */ symbols can be used to comment out an entire section, but only if the symbols appear at the beginning of a line (excluding whitespace), as in this example:

/*
MsgBox, This line is commented out (disabled).
MsgBox, Common mistake: */ this does not end the comment.
MsgBox, This line is commented out. 
*/

Since comments are ignored when a script is launched, they do not impact performance or memory utilization.

The default comment character (semicolon) can be changed to some other character or string via #CommentFlag.

Expressions

Expressions are combinations of one or more values, variables, operators and function calls. For example, 10, 1+1 and MyVar are valid expressions. Usually, an expression takes one or more values as input, performs one or more operations, and produces a value as the result. The process of finding out the value of an expression is called evaluation. For example, the expression 1+1 evaluates to the number 2.

Commands are designed to take a list of parameters and perform only a single action per line, whereas simple expressions can be pieced together to form increasingly more complex expressions. For example, if Discount/100 converts a discount percentage to a fraction, 1 - Discount/100 calculates a fraction representing the remaining amount, and Price * (1 - Discount/100) applies it to produce the net price.

Values are numbers, objects or strings. A literal value is one written physically in the script; one that you can see when you look at the code.

Strings / Text

For a more general explanation of strings, see Strings.

A string, or string of characters, is just a text value. In an expression, literal text must be enclosed in quotation marks to differentiate it from a variable name or some other expression. This is often referred to as a quoted literal string, or just quoted string. For example, "This is a quoted string.".

To include an actual quote character inside a quoted string, specify two consecutive quotes as shown twice in this example: "She said, ""An apple a day.""".

Quoted strings can contain escape sequences such as `t (tab), `n (linefeed), and `r (carriage return). Unlike unquoted text, it is not necessary to escape commas or percent signs, as quoted strings cannot contain variables. The use of the `" escape sequence to produce a literal quote-character is currently not supported; instead, use two consecutive quotes as shown above.

Variables

For a basic explanation and general details about variables, see Variables.

Variables can be used in an expression simply by writing the variable's name. For example, A_ScreenWidth/2. However, variables cannot be used inside a quoted string. Instead, variables and other values can be combined with text through a process called concatenation. There are two ways to concatenate values in an expression:

Implicit concatenation is also known as auto-concat. In both cases, the spaces preceding the variable and dot are mandatory.

The Format function can also be used for this purpose. For example:

MsgBox % Format("You are using AutoHotkey v{1} {2}-bit.", A_AhkVersion, A_PtrSize*8)

To assign a value to a variable, use the := assignment operator, as in MyVar := "Some text".

Percent signs within an expression are used to create dynamic variable references and dynamic function calls. Most of the time these constructs are not needed, so in general, variable names should not be enclosed in percent signs within an expression.

Operators

Operators take the form of a symbol or group of symbols such as + or :=, or one of the words and, or, not or new. They take one, two or three values as input and return a value as the result. A value or sub-expression used as input for an operator is called an operand.

Some unary and binary operators share the same symbols, in which case the meaning of the operator depends on whether it is written before, after or in between two values. For example, x-y performs subtraction while -x inverts the sign of x (producing a positive value from a negative value and vice versa).

Operators of equal precedence such as multiply (*) and divide (/) are evaluated in left-to-right order unless otherwise specified in the operator table. By contrast, an operator of lower precedence such as add (+) is evaluated after a higher one such as multiply (*). For example, 3 + 2 * 2 is evaluated as 3 + (2 * 2). Parentheses may be used to override precedence as in this example: (3 + 2) * 2

Function Calls

For a general explanation of functions and related terminology, see Functions/Commands.

Functions take a varying number of inputs, perform some action or calculation, and then return a result. The inputs of a function are called parameters or arguments. A function is called simply by writing its name followed by its parameters enclosed in parentheses. For example, GetKeyState("Shift") returns (evaluates to) 1 if Shift is being held down or 0 otherwise.

Note: There must not be any space between the function name and open parenthesis.

When compared to commands, the requirement for parentheses may seem cryptic or verbose at first, but they are what allows a function call to be combined with other operations. For example, the expression GetKeyState("Shift", "P") and GetKeyState("Ctrl", "P") returns 1 only if both keys are being physically held down.

Function names are always global, and are separate to variable names. For example, Round can be both a variable name and a function name, and Round := 1 will not affect Round(n) in any way.

Operators for Objects

There are other symbols used in expressions which don't quite fit into any of the categories defined above, or that affect the meaning of other parts of the expression, as described below. These all relate to objects in some way. Providing a full explanation of what each construct does would require introducing more concepts which are outside the scope of this section.

Alpha.Beta is often called member access. Alpha is an ordinary variable, and could be replaced with a function call or some other sub-expression which returns an object. When evaluated, the object is sent a request "give me the value of property Beta", "store this value in property Beta" or "call the method named Beta". In other words, Beta is a name which has meaning to the object; it is not a local or global variable.

Alpha.Beta() is a method call, as described above.

Alpha.Beta[Param] is a specialised form of member access which includes additional parameters in the request. While Beta is a simple name, Param is an ordinary variable or sub-expression, or a list of sub-expressions separated by commas (the same as in a function's parameter list).

Alpha[Index] has a similar function to Alpha.Beta, but each part is interpreted in a more standard way. That is, both Alpha and Index are variables in this case, and could be replaced with virtually any sub-expression. This syntax is usually used to retrieve an element of an array or associative array.

new ClassName() is used to instantiate a class, or create an object derived from another object. Although this looks like a function call, ClassName is actually an ordinary variable. Similarly, new Alpha.Beta() would create an object derived from the object returned by Alpha.Beta; Beta is neither a function nor a method. If the optional parentheses are present, they may contain parameters for the object's __New method.

[A, B, C] creates an array with the initial contents A, B and C (all variables in this case), where A is element 1.

{Key1: Value1, Key2: Value2} creates an associative array from a list of key-value pairs. A value can later be retrieved by its associated key. Writing a plain word (consisting of alphanumeric characters, underscore and non-ASCII characters) to the left of : is equivalent to enclosing that word in quotation marks. For example, {A: B} is equivalent to {"A": B}. However, {(A): B} uses the contents of the variable A as the key.

MyFunc(Params*) is a variadic function call. The asterisk must immediately precede the closing parenthesis at the end of the function's parameter list. Params must be a variable or sub-expression which returns an array object. Although it isn't valid to use Params* just anywhere, it can be used in an array literal ([A, B, C, ArrayToAppend*]) or indexer (Alpha[Params*]).

Expression Statements

Not all expressions can be used alone on a line. For example, a line consisting of just 21*2 or "Some text" wouldn't make any sense. An expression statement is an expression used on its own, typically for its side-effects. Most expressions with side-effects can be used this way, so it is generally not necessary to memorise the details of this section.

The following types of expressions can be used as statements:

Assignments, as in x := y, compound assignments such as x += y, and increment/decrement operators such as ++x and x--. However, in AutoHotkey v1, ++, --, +=, -=, *= and /= have slightly different behavior when used alone on a line, as they are actually equivalent to EnvAdd, EnvSub, EnvMult or EnvDiv. For details, see "Known limitations" under Assign in the table of operators.

Function calls such as MyFunc(Params). However, a standalone function call cannot be followed by an open brace { (at the end of the line or on the next line), because it would be confused with a function declaration.

Method calls such as MyObj.MyMethod().

Member access using square brackets, such as MyObj[Index], which can have side-effects like a function call.

Expressions starting with the new operator, as in new ClassName, because sometimes a class can be instantiated just for its side-effects.

Ternary expressions such as x ? CallIfTrue() : CallIfFalse(). However, it is safer to utilize the rule below; that is, always enclose the expression (or just the condition) in parentheses.

Note: Command names take precedence over ternary in AutoHotkey v1. For example, MsgBox ? 1 : 0 shows a message box.

Note: The condition cannot begin with ! or any other expression operator, as it would be interpreted as a continuation line.

Expressions starting with (. However, there usually must be a matching ) on the same line, otherwise the line would be interpreted as the start of a continuation section.

Expressions that start with any of those described above (but not those described below) are also allowed, for simplicity. For example, MyFunc()+1 is currently allowed, although the +1 has no effect and its result is discarded. Such expressions might become invalid in the future due to enhanced error-checking.

Member access using a dot (once or in a series), such as ExcelApp.Quit or x.y.z. However, unless parentheses are used (as in a method call), this cannot be the prefix of a larger expression. For example, ExcelApp.Quit, xxx is prohibited due to the apparent similarity to command syntax.

Legacy Syntax

Legacy or command syntax generally only allows a single action per line, but uses fewer characters to perform simple tasks such as sending keystrokes or running a program. The syntax consists of command and variable names, unquoted text and a few symbols such as ,, = and %.

Unquoted text is simply text, not enclosed in quote marks, just straight up. Since the text has no explicit start and end marks, it ends at the end of the line or the end of the parameter. Leading and trailing spaces and tabs are ignored. Within unquoted text, the following characters have special meaning:

Commands accept a mixture of unquoted text, variable names and numeric expressions.

Send, The time is %A_Hour% o'clock.

Legacy assignment assigns unquoted text to a variable.

Clipboard = This text is copied to the clipboard.

If statements perform an action only if the specified condition is met.

If Var = Text value

There are also several other control flow statements (such as loops) which use legacy syntax similar to commands.

Commands

A command is an instruction to carry out a specific predefined action. "Command" may also refer to a specific predefined action, such as MsgBox. The set of available commands is predefined and cannot be changed by the script.

A command is called simply by writing its name at the beginning of a line, optionally followed by parameters. For example:

MsgBox, The time is %A_Hour% o'clock.

The comma separating the command name from its parameters is optional, except in the following cases:

Each parameter of a command may accept different syntax, depending on the command. There are four types of parameters:

In most cases the percent prefix can be used to pass an expression.

OutputVar and InputVar Parameters

OutputVar and InputVar parameters require a variable name or dynamic variable reference. For example:

; Replace all spaces with pluses:
StringReplace, NewStr, OldStr, %A_Space%, +, All

This command reads the value from OldStr (the InputVar) and stores the result in NewStr (the OutputVar).

Note: Only a plain variable can be used as an OutputVar. Array elements, properties and other expressions are not supported.

InputVar parameters can accept an expression only when the percent prefix is used. However, the prefix is not supported in the Var parameters of legacy If commands, so If (expression) should be used instead.

Text Parameters

Text parameters accept unquoted text. For example:

MsgBox, The time is %A_Hour% o'clock.

Since commas and percent signs have special meaning, use the escape sequence `, to specify a literal comma and `% to specify a literal percent sign. For clarity, it is best to always escape any comma which is intended to be literal, but escaping comma is optional in the following cases:

To include a leading or trailing space or tab, use the built-in variables %A_Space% and %A_Tab% or a forced expression such as % " x ". [v1.1.06+]: Whitespace can also be preserved by preceding the space or tab with an escape character, except for whitespace at the end of a line.

Text parameters can also accept a forced expression.

Numeric Parameters

Numeric parameters accept a literal number or an expression, and can be identified by phrasing like "This parameter can be an expression."

For historical reasons, simple variable references alone or combined with digits are not interpreted as expressions. For example:

Sleep %n%000  ; Sleep for n seconds.
Sleep %m%     ; Sleep for m milliseconds.

To perform a double-deref in such cases, enclose the expression in parentheses: Sleep (%m%)

Note that mixed-type parameters such as SetTimer's second parameter, which sometimes accepts a number and sometimes accepts a string such as On or Off, are actually Text parameters, and as such, they do not accept expressions unless the percent prefix is used.

Numeric parameters allow and ignore the percent prefix.

% Expression

Although purely numeric parameters accept an expression by default, all other parameters of commands do not. Specify a percent sign followed by a space or tab to force a parameter to accept an expression. For example, all of the following are effectively identical because Sleep's first parameter is expression-capable:

Sleep MillisecondsToWait
Sleep %MillisecondsToWait%
Sleep % MillisecondsToWait

Note: Using the percent-space prefix in a numeric parameter does not necessarily force it to be an expression.

All parameters support the percent-space prefix except for:

Some users may find it easier to always force an expression, keeping to one consistent syntax (expression syntax) as much as possible.

Documentation Conventions

At the top of each page which documents a command, there is usually a block showing syntax, like this:

StringLower, OutputVar, InputVar , T

The square brackets denote optional parameters; the brackets themselves must be omitted from the actual code.

Sometimes the value a parameter accepts is written directly in the syntax block. For example, the third parameter of StringLower shown above accepts the letter T as text. The exact usage of a parameter is described in the Parameters section, and varies between commands.

Optional Parameters

Optional parameters can simply be left blank. The comma preceding an optional parameter can also be omitted if all subsequent parameters are omitted. For example, the Run command can accept between one and four parameters. All of the following are valid:

Run, notepad.exe, C:\
Run, notepad.exe,, Min
Run notepad.exe, , , notepadPID

Expressions vs Legacy Syntax

Many command parameters do not accept expressions by default. Use the percent-space prefix at the beginning of a parameter to evaluate that parameter as an expression. In the following examples, the expression is shown on the first line (beginning after the percent sign), with pure legacy syntax shown on the second line.

MsgBox % 1+1  ; Shows "2"
MsgBox   1+1  ; Shows "1+1"

Literal text in an expression is always enclosed in quote marks. These are called quoted strings.

MsgBox % "This is text."
MsgBox    This is text.

Variables in an expression are never enclosed in percent signs, except to create a double reference.

MsgBox %  A_AhkVersion
MsgBox   %A_AhkVersion%

Variables cannot be used inside a quoted string.

MsgBox % "Hello %A_UserName%."  ; Shows "%A_UserName%"
MsgBox    Hello %A_UserName%.   ; Shows your username.

Instead, values are concatenated by writing them in sequence, separated by a space or tab, or a dot surrounded by spaces.

MsgBox % "Hello " . A_UserName . "."  ; Shows your username.

One alternative is to use the Format function, which can also format the parameter value in various ways.

MsgBox % Format("Hello {1}.", A_UserName)  ; {} also works in place of {1}.

A value is assigned to a variable with := instead of =:

MyVar := "This is text."
MyVar = This is text.

Comparisons are performed using the same symbols as legacy If: =, <> or !=, >, >=, < and <=.

if (Var1 = Var2)
if Var1 = %Var2%

In an expression, both values can be simple values or complex sub-expressions. A comparison can also be combined with other conditions using operators such as and and or (which are equivalent to && and ||).

if (Var1 >= Low and Var1 <= High)
if Var1 between %Low% and %High%  

Different Equals

One common mistake is to write = where := is needed. For example:

Total = A + B   ; Assigns the literal text "A + B"

This can be difficult to avoid (at least until such time as the legacy assignment syntax is removed), but it may help to always use := where an assignment is intended.

The equal sign (when not used with another symbol, such as <=) has the following meanings:

The first two cases can be avoided by always using the := assignment operator and if (expression).

For the last three cases, := should have been used instead of =.

Commands vs Functions

In AutoHotkey v1, it is currently not possible to call a command from an expression, or to call a function using the command syntax. However, several commands have a function replacement.

CommandReplacement
FileAppendFileOpen and File.Write
FileGetAttribFileExist
FileReadFileOpen and File.Read
GetKeyStateGetKeyState (the function returns 0 or 1, not "U" or "D")
IfExistFileExist
IfInStringInStr
IfWinActiveWinActive
IfWinExistWinExist
OnExitOnExit (the function registers a callback function, not a subroutine)
StringGetPosInStr
StringLenStrLen
StringReplaceStrReplace
StringSplitStrSplit
StringLower
StringUpper
Format("{:L}", input), Format("{:U}", input) or Format("{:T}", input)
StringLeft
StringMid
StringRight
StringTrimLeft
StringTrimRight
SubStr

Control Flow Statements

For a general explanation of control flow, see Control Flow.

Statements are grouped together into a block by enclosing them in braces {}, as in C, JavaScript and similar languages, but usually the braces must appear at the start of a line. Control flow statements can be applied to an entire block or just a single statement.

The body of a control flow statement is always a single group of statements. A block counts as a single group of statements, as does a control flow statement and its body. The following related statements are also grouped with each other, along with their bodies: If with Else; Loop/For with Until; Try with Catch and/or Finally. In other words, when a group of these statements is used as a whole, it does not always need to be enclosed in braces (however, some coding styles always include the braces, for clarity).

Control flow statements which have a body and therefore must always be followed by a related statement or group of statements: If, Else, Loop, While, For, Try, Catch and Finally.

The following control flow statements exist:

Control Flow vs Commands

Control flow statements have syntax resembling commands, and are often referred to as such, but some differ from commands:

If Statement

If (expression) evaluates an expression and executes the following statement only if the result is true.

Common cause of confusion: There are several other types of If statements, some of which look very similar to If (expression). These should be avoided in new scripts. If in doubt, it is best to always begin the expression with an open-parenthesis. The "legacy" If statements are as follows:

Any If statement which does not match one of the usages shown above is interpreted as If (expression).

These are some common points of confusion related to legacy If statements:

The following "legacy" named If statements also exist:

With the exception of IfMsgBox, these are all obsolete and generally should be avoided in new scripts.

Named If statements allow a command to be written on the same line, but mispelled command names are treated as literal text. Such errors may be difficult to detect.

Loop Statement

There are several types of loop statements:

Break exits (terminates) a loop, effectively jumping to the next line after the loop's body.

Continue skips the rest of the current loop iteration and begins a new one.

Until causes a loop to terminate when an expression evaluates to true. The expression is evaluated after each iteration.

A label can be used to "name" a loop for Continue and Break. This allows the script to easily continue or break out of any number of nested loops without using Goto.

The built-in variable A_Index contains the number of the current loop iteration. It contains 1 the first time the loop's body is executed. For the second time, it contains 2; and so on. If an inner loop is enclosed by an outer loop, the inner loop takes precedence. A_Index works inside all types of loops, but contains 0 outside of a loop.

For some loop types, other built-in variables return information about the current loop item (registry key/value, file, substring or line of text). These variables have names beginning with A_Loop, such as A_LoopFileName and A_LoopReadLine. Their values always correspond to the most recently started (but not yet stopped) loop of the appropriate type. For example, A_LoopField returns the current substring in the innermost parsing loop, even if it is used inside a file or registry loop.

t := "column 1`tcolumn 2`nvalue 1`tvalue 2"
Loop Parse, t, `n
{
    rowtext := A_LoopField
    rownum := A_Index  ; Save this for use in the second loop, below.
    Loop Parse, rowtext, `t
    {
        MsgBox %rownum%:%A_Index% = %A_LoopField%
    }
}

Loop variables can also be used outside the body of a loop, such as in a function or subroutine which is called from within a loop.

Not Control Flow

As directives, labels (including hotkeys and hotstrings), and declarations without assignments are processed when the script is loaded from file, they are not subject to control flow. In other words, they take effect unconditionally, before the script ever executes any control flow statements. Similarly, the #If directives such as #IfWinActive cannot affect control flow; they merely set the criteria for any hotkey labels and hotstrings specified in the code. A hotkey's criteria is evaluated each time it is pressed, not when the #If directive is encountered in the code.

Structure of a Script

Auto-execute Section

After the script has been loaded, it begins executing at the top line, continuing until a Return, Exit, the script's first hotkey/hotstring label, or the physical end of the script is encountered (whichever comes first). This top portion of the script is referred to as the auto-execute section, but it is really just a subroutine which is called after program startup.

Note: While the script's first hotkey/hotstring label has the same effect as return, other hotkeys and labels do not.

The auto-execute section is often used to configure settings which apply to every newly launched thread. For details, see The Top of the Script.

Subroutines

A subroutine (or sub) is a reusable block of code which can be called to perform some task.

Scripts use subroutines to define what should happen when a particular hotkey is pressed or some other event occurs. Scripts can also call subroutines directly, by using Gosub.

Any label can be used as the starting point of a subroutine. A subroutine has no explicitly marked ending point, but instead ends if and when control is returned to the subroutine's caller by Return or when the thread is exited. For example:

gosub Label1

Label1:
MsgBox %A_ThisLabel%
return

Note that as labels have no effect when reached during normal execution, in this example a message box would be shown twice: once while the subroutine is running and again after it returns. One important consequence is that you cannot define one subroutine inside another subroutine, because the "body" of the inner subroutine would execute automatically and then return, effectively terminating the outer subroutine.

Subroutines should typically be defined separately to any other block of code, but can also be defined inside a function, allowing the subroutine access to that function's static variables (and local variables, but only while the function is running).

Note: Subroutines defined inside a function have certain limitations regarding the use of local variables and dynamic variable references, including Gui control variables. For details, see Using Subroutines Within a Function.

User-Defined Functions

Generally speaking, a function is a kind of subroutine. However, within the AutoHotkey documentation, "subroutine" typically refers to the kind of subroutine defined by a label (described above).

User-defined functions differ from subroutines in that they can accept parameters and return a value, and they can have local variables. They can be called either by a function call within the script or by the program itself, such as if a function was passed to the Hotkey or SetTimer commands.

Functions are defined using syntax resembling a function call followed by a block of code enclosed in braces:

MyFunction(FirstParameter, Second, ByRef Third, Fourth:="")
{
    ...
    return "a value"
}

As with function calls, there must be no space between the function name and open-parenthesis.

The line break between the close-parenthesis and open-brace is optional. There can be any amount of whitespace or comments between the two.

ByRef indicates that the parameter accepts a variable reference, making that parameter an alias for whichever variable the caller passes. If the caller does not pass a variable, the parameter acts as a normal local variable. ByRef parameters can also be optional.

Optional parameters are specified by following the parameter name with := or = and a default value, which must be a literal quoted string, a number, true or false. The operators := and = are interchangeable for historical reasons, but it is best to use := for consistency with assignment in expressions.

The function can return a value. If it does not, the default return value is an empty string.

A function cannot be defined inside another function. Otherwise, the position of a function definition does not matter; any function defined within the script can be called from anywhere else.

See Functions for much more detail.

#Include

The #Include directive causes the script to behave as though the specified file's contents are present at this exact position. This is often used to organise code into separate files, or to make use of script libraries written by other users.

Note: The following paragraphs detail some common points of confusion.

When using #Include, it is important to consider what effect the file's contents would have if placed at that position, since #Include will have the same effect. For instance:

#Include can be safely used within the auto-execute section to include files which contain only function definitions, since function definitions (but not function calls) are skipped over during execution. If a file contains other code, one can avoid breaking the auto-execute section by skipping over the file's contents with Goto.

Unlike in C/C++, #Include does nothing if the file has already been included by a previous directive. To include the contents of the same file multiple times, use #IncludeAgain.

Script files containing functions can be automatically included without having to use #Include, if they are saved in a standard location and named appropriately. The effect is similar to using #Include at the end of the main script file. For details, see Libraries of Functions.

Miscellaneous

Dynamic Variables

A dynamic variable reference takes a text value and interprets it as the name of a variable.

The most common form of dynamic variable reference is called a double reference or double-deref. Before performing a double reference, the name of the target variable is stored in a second variable. This second variable can then be used to assign a value to the target variable indirectly, using a double reference. For example:

target := 42
second := "target"
MsgBox   %second%  ; Normal (single) variable reference in text => target
MsgBox %  second   ; Normal (single) variable reference in an expression => target
MsgBox % %second%  ; Double-deref in an expression => 42

At first, it would appear that percent signs have a different meaning depending on whether they are used in text or in an expression. However, it may make more sense to think of %second% as being replaced with the contents of the variable second in both cases:

Currently, second must always contain a variable name in the second case; arbitrary expressions are not supported.

A dynamic variable reference can also take one or more pieces of literal text and the content of one or more variables, and join them together to form a single variable name. This is done simply by writing the pieces of the name and percent-enclosed variables in sequence, without any spaces. For example, MyArray%A_Index% or MyGrid%X%_%Y%. This is used to access pseudo-arrays, described below.

For a description of how dynamic variable references inside functions are resolved, see Functions: More About Locals and Globals.

Pseudo-arrays

A pseudo-array is actually just a bunch of discrete variables, but with a naming pattern which allows them to be used like elements of an array. For example:

MyArray1 = A
MyArray2 = B
MyArray3 = C
Loop 3
    MsgBox % MyArray%A_Index%  ; Shows A, then B, then C.

As the individual elements are just normal variables, one can assign or retrieve a value, but cannot remove or insert elements. Because the pseudo-array itself doesn't really exist, it can't be passed to or returned from a function, or copied as a whole. For these reasons, it is generally recommended to use normal arrays instead, where possible.

Associative pseudo-arrays

The "index" used to form the final variable name does not have to be numeric; it could instead be a letter or keyword, making the pseudo-array similar to an associative array or an object. The following example creates a pseudo-array with elements "Left", "Top", "Right" and "Bottom":

SysGet, WA, MonitorWorkArea
MsgBox, Left: %WALeft% -- Top: %WATop% -- Right: %WARight% -- Bottom: %WABottom%.

Commands which create pseudo-arrays

There are several commands which create associative pseudo-arrays:

Caution: These commands do not follow the same rules as dynamic variable references. If used within a function, the resulting pseudo-array is either entirely global or entirely local, depending only on the first element (or base name) of the array. Some of the variables in the pseudo-array may be inaccessible if they are not individually declared. For details, see Functions: More About Locals and Globals.

AutoHotkey also creates one global pseudo-array to contain any command line parameters that were passed to the script.

Labels

A label identifies a line of code, and can be used as a Goto target or to form a subroutine. There are three kinds of label: normal named labels, hotkey labels and hotstring labels.

Normal labels consist of a name followed by a colon.

this_is_a_label:

Hotkey labels consist of a hotkey followed by double-colon.

^a::

Hotstring labels consist of a colon, zero or more options, another colon, an abbreviation and double-colon.

:*:btw::

Generally, aside from whitespace and comments, no other code can be written on the same line as a label. However:

For more details, see Labels.