Thoughts for v2.0

Commands

Command()

As commands and functions are still internally distinct in the current v2 alpha, defining a function with the same name as a command overrides the command only for the function() style of calling; i.e. the command style still calls the original command. This should be changed.

Commands with sub-commands

The remaining commands which have sub-commands are: DriveGet, Drive, Control, ControlGet, Gui, GuiControl and GuiControlGet.

Gui, GuiControl and GuiControlGet are being replaced with GuiCreate() and Gui/GuiControl objects.

DriveGet:

Drive:

Control:

ControlGet:

Click / MouseClick

Rename MouseClickDrag, perhaps to MouseDrag?

MouseClick has been superseded by Click. Should MouseClick be removed? Should Click be renamed to MouseClick, for consistency with MouseMove?

FileRead / FileAppend

These have rather inconsistent syntax. Maybe do this:

FileRead, OutputVar, Filename [, Options]
FileAppend [, Text, Filename, Options]

Not sure whether/how to make EOL translations (and options) more consistent.

Also consider whether FileRead *c should be changed/replaced (see also ClipboardAll below). Moving the functionality to the File object could improve flexibility. However, FileRead *c also provides a convenient way to read a file's entire binary contents into memory. (For that, maybe a "Raw" option/encoding name would be better.)

WinTitle and Window Groups

Background info: Each window group consists of a list of window specs. A window spec in v1 consists of WinTitle, WinText, ExcludeTitle and ExcludeText. Window specs are evaluated in the order they were added to the group. For convenience, "rule" means "window spec" in the text below.

Exclusions

Problems in v1:

The most likely solution is to introduce "negative" or "exclusive" rules. Rules are evaluated in the order they were added to the group, ending with the first rule (positive or negative) that matches the window. This provides the following benefits:

There are different ways this could be achieved:

Chris' comments:

ExcludeTitle doesn't support ahk_pid and similar things. All it does is what the help file says: "Windows whose titles include this value will not be considered." The reason for this is that the added code size doesn't seem justified given how rarely ExcludeTitle is used.

In light of the above, the second option above seems preferable.

In either case, a negative rule has no effect if there aren't any positive rules following it, because the absence of any subsequent positive rules means that the window won't be a match anyway. Therefore, for convenience, a group could match all windows by default if the last rule is negative. That way, if group N contains only negative rules, ahk_group N matches all windows except those excluded by the group (instead of always matching nothing at all).

Settings

An oft-requested feature is to allow global settings such as SetTitleMatchMode, DetectHiddenWindows and DetectHiddenText to be overruled temporarily for a given WinTitle or command. At the moment, the most likely form it could take is Title ahk_mode 3 ahk_dhw 1. Implementing this in a later release is unlikely to affect existing scripts; therefore this is not currently planned for v2.0.

Binary Data

Strings in v2-alpha can contain binary zero, but there is room for improvement. SubStr is able to return a portion of the data, but with a granularity of 2 bytes (because of UTF-16). Scripts which work with variables and array elements as buffers (with VarSetCapacity/SetCapacity) have no way to set the length of meaningful data - i.e. VarSetLength or similar.

A more general approach would be to create a string of n length, assign that to the variable or object, then manipulate the content. However, with the current implementation, that might require copying the string and redundant memory allocations.

Scripts use VarSetCapacity for two purposes:

The latter case should use VarSetLength, since the entire length of the structure is meaningful. This would allow a struct to be passed by value (copied) or returned from a function. VarSetCapacity would then only be used for strings; maybe the capacity should be measured in characters rather than bytes.

ClipboardAll

Currently ClipboardAll has its own special type of value which only supports specific operations.

When used in other ways, it is blank. ClipboardAll should be changed to use a more intuitive mechanism.

As string values can now contain binary zero, ClipboardAll values could simply be strings. The "clipboard binary data" attribute (which currently applies to the variable which contains the value, not the value itself) could be replaced with a specific "signature" within the data to identify it as clipboard data.

Alternatively, ClipboardAll could return an object. This object would initially support some way to retrieve the binary data. It should also be possible to easily write the data to file, and to construct an object from binary data or a file. The object could later be extended to support enumeration of data types, retrieval of a specific data type, and perhaps adding data.

It might be best to replace ClipboardAll with a function, such as ClipboardGetAll(), to reflect the fact that you cannot assign to ClipboardAll, and that it constructs a new object/entire copy of the clipboard each time.

Also consider whether FileRead *c should be changed/replaced (see FileRead above).

Expression Operators

Although in and contains are already reserved in v2-alpha (so adding them later won't break anything), v2 might seem incomplete without them, since users converting existing scripts from v1 (or just used to using v1) will need to find some alternative that works in v2. They should probably be implemented before the v2.0 release, and they should be updated to be more modern (allow arrays).

Current usage: if var [not] in/contains MatchList. MatchList is a comma-delimited list. Var contains a string.

It seems logical to allow an array as a direct substitute for MatchList. Someone suggested that the new operators should never accept a comma-delimited list, but in that case a string would be interpreted one of the following ways:

  1. An error.
  2. Equivalent to a single-element array. That would mean:
  3. Equivalent to an array of the string's individual characters. That would mean:
  4. Having different meaning depending on the type of operand. Seems confusing. For example:

If arrays are supported on the left side of contains, how would that work? Traditionally the difference between in and contains is that in does a direct comparison of the value while contains does a substring match. Would arr1 contains arr2 search for each element of arr2 as a substring of each element of arr1? Or check for the existence of any common values between the two? Or all? Or check whether the exact sequence of values in arr2 exists within arr1 as a subset? The last option would be analogous to InStr(str1, str2), but not str1 contains str2.

Some (not very relevant) discussion here: https://autohotkey.com/boards/viewtopic.php?f=37&t=3847

between probably won't be implemented at any stage. An alternative could be to allow operator chaining as in Python and Perl 6, where x < y < z is equivalent to (x < y) and (y < z). However, that would mean that parentheses would change the meaning of < (which doesn't seem right), such as in x < y < z vs (x < y) < z, because the latter case would compare the result of x < y (true or false) to z. Some users already try x < y < z and wonder at the result, so if it isn't implemented, maybe chained operators should be detected and cause a warning/error. (But if chained operators would be complicated to detect, cost vs benefit seems to be in favour of implementing chaining.)

Error Handling

Note that some changes have already been made which won't be covered here.

Errors in math operations

In v1, math operators silently produce an empty string in the following cases:

It's uncertain whether behaviour under these conditions should stay as is. The alternatives are to throw an exception or to produce some other non-numeric value, such as the string "NaN" (reminiscent of JavaScript's special NaN value). The latter seems like a worse fit for AutoHotkey.

Additionally, there have been debates about whether empty strings should be treated as 0. This will most likely not happen, with the exception of += and -=, which have been changed to treat an empty variable as 0, since these are where the policy is most commonly convenient.

Errors in math functions

If the behaviour of math operators is changed (to do something other than return an empty string on error), math functions such as Round() should be changed accordingly.

Objects

Syntax for properties vs methods

Built-in objects and user-defined objects are inconsistent in what syntax they support for properties. This should be rectified where possible.

How it works now:

What should probably be changed:

Inconsistent properties to consider changing:

Meta-Functions

Meta-functions are commonly misunderstood, and should probably be simplified. Random ideas:

Other

Object.GetAddress(key) should return the address of an empty string if that's what Object[key] contains, since some scripts may want to assign a string and then pass its address to DllCall or similar. Currently such scripts need to call SetCapacity after assigning an empty string.

Objects now support binary zero in string values, by storing the length of the string. However, now only data up to the string length is preserved when cloning the object (consistent with retrieving the value from the object as a string, such as to assign to another object). There should be some way to set the length of data, such has Object.SetLength(Key, Length). For clarity (and to avoid confusion with Object.Length), it might be better to have SetStrLen and SetByteLen. Also consider that similar functions might be needed for variables, to better support binary data.

Some users have requested Object.Count(). Usage could be Object.Count([MinKey, MaxKey]), returning the number of key-value pairs in that range (or in total if there are no parameters).

new MyClass[a,b,c] could be reserved as a more convenient way to initialize a specialised array/collection type (new MyClass{a:b} is already "reserved" by way of being treated as a syntax error). Currently MyClass[a,b,c] could be a class to instantiate, but it's unlikely to have been used much (if at all), and is inconsistent with the v1 policy of requiring a class name after the new operator. v1 treats new x[y](z) as new c1 where c1 = x[y](z), while v2-a076 treats it as new c2(z) where c2 = x[y].

SafeArray objects should perhaps gain a .Length() method for consistency with plain Objects.

ComObjConnect() events currently dereference ByRef VARIANTs automatically (since some languages pass this whenever a variable is passed from script), which prevents the function from being able to pass back a value in some cases. We could either a) pass a ByRef wrapper allowing assignments such as param[] := value or b) automatically pass a variable and update the VARIANT after the function is called. Other (i.e. non-VARIANT) ByRef combinations are not dereferenced, and param[] := value is already supported in v1.1.17+.

Classes

Allow base to be defined as a nested class (and actually set the base class), so that meta-functions and properties can be easily defined for the class itself, within the class.

Make base a reserved word in class methods. Currently it is given special meaning when followed by . or [, except when the variable base has been assigned a value. Reserving the word simplifies the language and may improve performance.

If base is reserved everywhere, it could be used for calling an overridden built-in function (such as base.WinMove(x, y) in a redefinition of WinMove).

Ideas

These were tentative ideas. Encouraging "OO" syntax is not my intended direction for AutoHotkey (especially with numbers and strings), so these probably won't be implemented.

Allow the script to define classes String and Number with methods and properties for all strings or numbers. Currently this is done by setting properties of the "".base object, but this affects all types of values except objects.

Allow the script to define global methods and properties for all objects, perhaps via an Object base class. By default this class would contain built-in methods such as Insert, allowing them to be redefined the same way that user-defined methods are. For example, a user-defined Insert method might call base.Insert(value) instead of ObjInsert(this, value).

Both of the above may require some way to append to an existing class, which may also be useful for user-defined classes. This could be allowed simply by repeating class ClassName {} or with an additional keyword for clarity, such as class ClassName extended {}. The keyword might imply that the initial and extended definitions must be placed in that specific order (rather than having two similar definitions which can be placed in any order), which mightn't be ideal. Another option is to allow ClassName.MethodName() declarations outside classes, allowing existing classes to be extended (and also allowing more flexibility over the organization of code).