AHK v2: 4 mods to enable two-way compatibility

Talk about things C/C++, some related to AutoHotkey
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

AHK v2: 4 mods to enable two-way compatibility

Post by jeeswg » 06 Nov 2019, 20:57

4 EDITS TO AHK V2: SCRIPTS THAT WORK IN BOTH AHK V1/V2

I made 4 edits to the AHK v2 source code, that allowed AHK v2 to run both AHK v1/v2 code.
(Note: where the AHK v1 code is written in a forwards compatible way and uses custom backport functions.)

This is also mentioned here:
conversion logic, v1 = -> v1 := -> v2, two-way compatibility - Page 7 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=37&t=27069&p=280143#p280143

EDIT 1: NUMERIC/STRING COMPARISON

One unintuitive change was made to numeric/string comparison in AHK v2. The main dev has suggested it ought to be changed back.

Code: Select all

var1 := "1"
var2 := "1.0"
MsgBox(var1 = var2) ;0 (AHK v2 at present) ;1 (AHK v2 when fix is applied, AHK v1)
My conclusion for a simple/effective set of rules is: if two items look numeric, compare numerically, otherwise alphabetically. Use StrCompare (and perhaps introduce StrEquals) to force an alphabetic comparison.

See also:
numbers/strings in expressions - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=37&t=55905

EDIT 2: CONTROLXXX FUNCTIONS: ALLOW OMITTING 'CONTROL' PARAMETER

The Control parameter in most ControlXXX functions was made mandatory.
I found the previous behaviour logical and intuitive: check the WinXXX parameters, then the Control parameter (if present) to determine which window/control window receives the message.
Also, maintaining the previous behaviour greatly eases script conversion, cf. manual checking/editing.
Furthermore, some ControlXXX functions still allow 'Control' to be omitted. Allowing 'Control' to be omitted for all ControlXXX functions gives consistency, and a memorable rule.

Code: Select all

hCtl := ControlGetHwnd("Edit1", "ahk_class Notepad")
vText := ControlGetText(, "ahk_id " hCtl) ;no longer allowed
vText := ControlGetText(hCtl)

EDIT 3: ALLOW #IFWINACTIVE (UNTIL #IF WINACTIVE() IS OPTIMISED IN AHK V1)

#If WinActive("ahk_class Notepad") etc, is doable in AHK v1, but it is slow.
So until/unless #If WinActive() is optimised in AHK v1, it is better to continue using #IfWinActive.
The edit allows you to run AHK v1 scripts without changing all of the #IfWinActive lines.

Code: Select all

#IfWinActive ahk_class Notepad ;AHK v1 only
#If WinActive("ahk_class Notepad") ;AHK v1/v2 ;preferable

EDIT 4: ALLOW '% ' IN LOOPS (UNTIL A TWO-WAY COMPATIBLE AHK V1/V2 LOOP IS INTRODUCED)

The biggest obstacle to two-way compatible code is as follows:

Code: Select all

Loop Parse, vText, % "`n", % "`r" ;AHK v1 only
Loop Parse, vText, "`n", "`r" ;AHK v2 only

LoopParse vText, "`n", "`r" ;AHK v1/v2 my proposal
So until a two-way compatible LoopXXX syntax is introduced to AHK v1/v2, allowing forced expressions in AHK v2, solely for the Loop control flow statement, is the best temporary solution.
The edit allows the '% ' to remain in AHK v2.

CODE AND NOTES

Code: Select all

AHK v2 4 mods (tested on AutoHotkey v2.0-a106)
note: compiled with VS 2017 (compiling did not work with VS 2013)

[download source code]
Releases · Lexikos/AutoHotkey_L · GitHub
https://github.com/Lexikos/AutoHotkey_L/releases

[tips re. compiling]
AutoHotkey C++ Powerhouse: Introduction - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=75&t=54394

[notes re. installing Visual Studio/compiling AutoHotkey_H]
[Guide] Compiling Ahk_h's source step by step - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=65&t=62308

[summary of 4 modifications]
conversion logic, v1 = -> v1 := -> v2, two-way compatibility - Page 7 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=37&t=27069&p=280143#p280143

[all 4 modifications in 1 example]
#IfWinActive ahk_class Notepad
;#If WinActive("ahk_class Notepad")
q::
hCtl := ControlGetHwnd("Edit1", "ahk_class Notepad")
vText := ControlGetText(, "ahk_id " hCtl)
;vText := ControlGetText(hCtl)
MsgBox(vText)

Loop Parse, vText, % "`n", % "`r"
;Loop Parse, vText, "`n", "`r"
	vCount := A_Index
MsgBox(vCount)

var1 := "1"
var2 := "1.0"
MsgBox(var1 = var2) ;0 ;1 when fix is applied
MsgBox(0 + var1 = 0 + var2) ;1
#If

==================================================

[if (a = b): restore the numeric comparison of string variables][redundant if the behaviour is restored in AHK v2]
Revised handling of types (integer/float/string). · Lexikos/AutoHotkey_L@275216e · GitHub
https://github.com/Lexikos/AutoHotkey_L/commit/275216e20724568ba7afbe6f2cc29297fd162be2

note: this doesn't restore all of the AHK v1 string/numeric comparison behaviour

compare these numerically again:
var1 := "1"
var2 := "1.0"
MsgBox(var1 = var2) ;0 ;1 when fix is applied
MsgBox(0 + var1 = 0 + var2) ;1

[script_expression.cpp]
[BEFORE]
			if (  !(right_is_number && left_is_number)  // i.e. they're not both numeric (or this is SYM_CONCAT).
				|| IS_RELATIONAL_OPERATOR(this_token.symbol) && !right_is_pure_number && !left_is_pure_number  ) // i.e. if both are strings, compare them alphabetically.
			{
[BEFORE END]

[AFTER]
			if (  !(right_is_number && left_is_number)  // i.e. they're not both numeric (or this is SYM_CONCAT).
				//==============================
				//jeeswg edit: code modified
				// || IS_RELATIONAL_OPERATOR(this_token.symbol) && !right_is_pure_number && !left_is_pure_number  ) // i.e. if both are strings, compare them alphabetically.
				|| false)
				//==============================
			{
[AFTER END]

==================================================

[ControlXXX: allow omitting the 'Control' parameter][redundant if 'Control' is made omissible again for all ControlXXX functions in AHK v2]
Changed usage of Control parameter. · Lexikos/AutoHotkey_L@01f61cb · GitHub
https://github.com/Lexikos/AutoHotkey_L/commit/01f61cbd676026e29b15e8043cefc2260b288a9a

make Control parameter in ControlXXX functions optional
make this possible again:
hCtl := ControlGetHwnd("Edit1", "ahk_class Notepad")
vText := ControlGetText(, "ahk_id " hCtl)
MsgBox(vText)

[script.cpp]
[BEFORE]
	BIFn(ControlAddItem, 2, 6, BIF_Control),
	BIFn(ControlChoose, 2, 6, BIF_Control),
	BIFn(ControlChooseString, 2, 6, BIF_Control),
	BIF1(ControlClick, 0, 8),
	BIFn(ControlDeleteItem, 2, 6, BIF_Control),
	BIFn(ControlEditPaste, 2, 6, BIF_Control),
	BIFn(ControlFindItem, 2, 6, BIF_ControlGet),
	BIF1(ControlFocus, 1, 5),
	BIFn(ControlGetChecked, 1, 5, BIF_ControlGet),
	BIFn(ControlGetChoice, 1, 5, BIF_ControlGet),
	BIF1(ControlGetClassNN, 1, 5),
	BIFn(ControlGetCurrentCol, 1, 5, BIF_ControlGet),
	BIFn(ControlGetCurrentLine, 1, 5, BIF_ControlGet),
	BIFn(ControlGetEnabled, 1, 5, BIF_ControlGet),
	BIFn(ControlGetExStyle, 1, 5, BIF_ControlGet),
	BIF1(ControlGetFocus, 0, 4),
	BIFn(ControlGetHwnd, 1, 5, BIF_ControlGet),
	BIFn(ControlGetLine, 2, 6, BIF_ControlGet),
	BIFn(ControlGetLineCount, 1, 5, BIF_ControlGet),
	BIFn(ControlGetList, 0, 6, BIF_ControlGet),
	BIF1(ControlGetPos, 0, 9, {1, 2, 3, 4}),
	BIFn(ControlGetSelected, 1, 5, BIF_ControlGet),
	BIFn(ControlGetStyle, 1, 5, BIF_ControlGet),
	BIFn(ControlGetTab, 1, 5, BIF_ControlGet),
	BIF1(ControlGetText, 1, 5),
	BIFn(ControlGetVisible, 1, 5, BIF_ControlGet),
	BIFn(ControlHide, 1, 5, BIF_Control),
	BIFn(ControlHideDropDown, 1, 5, BIF_Control),
	BIF1(ControlMove, 0, 9),
	BIFn(ControlSend, 1, 6, BIF_ControlSend),
	BIFn(ControlSendText, 1, 6, BIF_ControlSend),
	BIFn(ControlSetChecked, 2, 6, BIF_Control),
	BIFn(ControlSetEnabled, 2, 6, BIF_Control),
	BIFn(ControlSetExStyle, 2, 6, BIF_Control),
	BIFn(ControlSetStyle, 2, 6, BIF_Control),
	BIFn(ControlSetTab, 2, 6, BIF_Control),
	BIF1(ControlSetText, 2, 6),
	BIFn(ControlShow, 1, 5, BIF_Control),
	BIFn(ControlShowDropDown, 1, 5, BIF_Control),
[BEFORE END]

[AFTER]
	//==============================
	//jeeswg edit: code modified
	BIFn(ControlAddItem, 1, 6, BIF_Control),
	BIFn(ControlChoose, 1, 6, BIF_Control),
	BIFn(ControlChooseString, 1, 6, BIF_Control),
	BIF1(ControlClick, 0, 8),
	BIFn(ControlDeleteItem, 1, 6, BIF_Control),
	BIFn(ControlEditPaste, 1, 6, BIF_Control),
	BIFn(ControlFindItem, 1, 6, BIF_ControlGet),
	BIF1(ControlFocus, 0, 5),
	BIFn(ControlGetChecked, 0, 5, BIF_ControlGet),
	BIFn(ControlGetChoice, 0, 5, BIF_ControlGet),
	BIF1(ControlGetClassNN, 0, 5),
	BIFn(ControlGetCurrentCol, 0, 5, BIF_ControlGet),
	BIFn(ControlGetCurrentLine, 0, 5, BIF_ControlGet),
	BIFn(ControlGetEnabled, 0, 5, BIF_ControlGet),
	BIFn(ControlGetExStyle, 0, 5, BIF_ControlGet),
	BIF1(ControlGetFocus, 0, 4),
	BIFn(ControlGetHwnd, 0, 5, BIF_ControlGet),
	BIFn(ControlGetLine, 1, 6, BIF_ControlGet),
	BIFn(ControlGetLineCount, 0, 5, BIF_ControlGet),
	BIFn(ControlGetList, 0, 6, BIF_ControlGet),
	BIF1(ControlGetPos, 0, 9, {1, 2, 3, 4}),
	BIFn(ControlGetSelected, 0, 5, BIF_ControlGet),
	BIFn(ControlGetStyle, 0, 5, BIF_ControlGet),
	BIFn(ControlGetTab, 0, 5, BIF_ControlGet),
	BIF1(ControlGetText, 0, 5),
	BIFn(ControlGetVisible, 0, 5, BIF_ControlGet),
	BIFn(ControlHide, 0, 5, BIF_Control),
	BIFn(ControlHideDropDown, 0, 5, BIF_Control),
	BIF1(ControlMove, 0, 9),
	BIFn(ControlSend, 1, 6, BIF_ControlSend),
	BIFn(ControlSendText, 1, 6, BIF_ControlSend),
	BIFn(ControlSetChecked, 1, 6, BIF_Control),
	BIFn(ControlSetEnabled, 1, 6, BIF_Control),
	BIFn(ControlSetExStyle, 1, 6, BIF_Control),
	BIFn(ControlSetStyle, 1, 6, BIF_Control),
	BIFn(ControlSetTab, 1, 6, BIF_Control),
	BIF1(ControlSetText, 1, 6),
	BIFn(ControlShow, 0, 5, BIF_Control),
	BIFn(ControlShowDropDown, 0, 5, BIF_Control),
	//==============================
[AFTER END]

==================================================

[#IfWin: restore #IfWinActive][redundant if #If WinXXX() is optimised in AHK v1]
Removed #IfWin and optimized #If Win(). · Lexikos/AutoHotkey_L@e4a5493 · GitHub
https://github.com/Lexikos/AutoHotkey_L/commit/e4a5493749362b11c83ff71a9695782e72f82c65

allow this AHK v1 code in AHK v2:
#IfWinActive ahk_class Notepad

[ABOVE EDIT]
	if (IS_DIRECTIVE_MATCH(_T("#IfTimeout")))
	{
		if (parameter)
			g_HotExprTimeout = ATOU(parameter);
		return CONDITION_TRUE;
	}
[ABOVE EDIT END]

[EDIT]
	//==============================
	//jeeswg edit: code inserted
	if (!_tcsnicmp(aBuf, _T("#IfWin"), 6))
	{
		HotCriterionType hot_criterion;
		bool invert = !_tcsnicmp(aBuf + 6, _T("Not"), 3);
		if (!_tcsnicmp(aBuf + (invert ? 9 : 6), _T("Active"), 6)) // It matches #IfWin[Not]Active.
			hot_criterion = invert ? HOT_IF_NOT_ACTIVE : HOT_IF_ACTIVE;
		else if (!_tcsnicmp(aBuf + (invert ? 9 : 6), _T("Exist"), 5))
			hot_criterion = invert ? HOT_IF_NOT_EXIST : HOT_IF_EXIST;
		else // It starts with #IfWin but isn't Active or Exist: Don't alter g->HotCriterion.
			return CONDITION_FALSE; // Indicate unknown directive since there are currently no other possibilities.
		if (!parameter) // The omission of the parameter indicates that any existing criteria should be turned off.
		{
			g->HotCriterion = NULL; // Indicate that no criteria are in effect for subsequent hotkeys.
			return CONDITION_TRUE;
		}
		LPTSTR hot_win_title = parameter, hot_win_text; // Set default for title; text is determined later.
		// Scan for the first non-escaped comma.  If there is one, it marks the second parameter: WinText.
		LPTSTR cp, first_non_escaped_comma;
		for (first_non_escaped_comma = NULL, cp = hot_win_title; ; ++cp)  // Increment to skip over the symbol just found by the inner for().
		{
			for (; *cp && !(*cp == g_EscapeChar || *cp == g_delimiter || *cp == g_DerefChar); ++cp);  // Find the next escape char, comma, or %.
			if (!*cp) // End of string was found.
				break;
#define ERR_ESCAPED_COMMA_PERCENT _T("Literal commas and percent signs must be escaped (e.g. `%)")
			if (*cp == g_DerefChar)
				return ScriptError(ERR_ESCAPED_COMMA_PERCENT, aBuf);
			if (*cp == g_delimiter) // non-escaped delimiter was found.
			{
				// Preserve the ability to add future-use parameters such as section of window
				// over which the mouse is hovering, e.g. #IfWinActive, Untitled - Notepad,, TitleBar
				if (first_non_escaped_comma) // A second non-escaped comma was found.
					return ScriptError(ERR_ESCAPED_COMMA_PERCENT, aBuf);
				// Otherwise:
				first_non_escaped_comma = cp;
				continue; // Check if there are any more non-escaped commas.
			}
			// Otherwise, an escape character was found, so skip over the next character (if any).
			if (!*(++cp)) // The string unexpectedly ends in an escape character, so avoid out-of-bounds.
				break;
			// Otherwise, the ++cp above has skipped over the escape-char itself, and the loop's ++cp will now
			// skip over the char-to-be-escaped, which is not the one we want (even if it is a comma).
		}
		if (first_non_escaped_comma) // Above found a non-escaped comma, so there is a second parameter (WinText).
		{
			// Omit whitespace to (seems best to conform to convention/expectations rather than give
			// strange whitespace flexibility that would likely cause unwanted bugs due to inadvertently
			// have two spaces instead of one).  The user may use `s and `t to put literal leading/trailing
			// spaces/tabs into these parameters.
			hot_win_text = omit_leading_whitespace(first_non_escaped_comma + 1);
			*first_non_escaped_comma = '\0'; // Terminate at the comma to split off hot_win_title on its own.
			rtrim(hot_win_title, first_non_escaped_comma - hot_win_title);  // Omit whitespace (see similar comment above).
			// The following must be done only after trimming and omitting whitespace above, so that
			// `s and `t can be used to insert leading/trailing spaces/tabs.  ConvertEscapeSequences()
			// also supports insertion of literal commas via escaped sequences.
			ConvertEscapeSequences(hot_win_text, NULL);
		}
		else
			hot_win_text = _T(""); // And leave hot_win_title set to the entire string because there's only one parameter.
		// The following must be done only after trimming and omitting whitespace above (see similar comment above).
		ConvertEscapeSequences(hot_win_title, NULL);
		// The following also handles the case where both title and text are blank, which could happen
		// due to something weird but legit like: #IfWinActive, ,
		if (!SetHotkeyCriterion(hot_criterion, hot_win_title, hot_win_text))
			return ScriptError(ERR_OUTOFMEM); // So rare that no second param is provided (since its contents may have been temp-terminated or altered above).
		return CONDITION_TRUE;
	} // Above completely handles all directives and non-directives that start with "#IfWin".
	//==============================
[EDIT END]

[BELOW EDIT]
	if (IS_DIRECTIVE_MATCH(_T("#Hotstring")))
[BELOW EDIT END]

==================================================

[Loop/Loop XXX: allow '% Expression'][redundant if Loop (expression)/LoopXXX are agreed for AHK v1/v2]
Changed commands to accept expressions by default. · Lexikos/AutoHotkey_L@4f8d55d · GitHub
https://github.com/Lexikos/AutoHotkey_L/commit/4f8d55dfddb6135c1d32584cb42ccefca0260e9d

allow this AHK v1 code in AHK v2:
Loop Parse, vText, % "`n", % "`r"

[ABOVE EDIT]
	for (nArgs = mark = 0; action_args[mark] && nArgs < max_params; ++nArgs)
	{
		arg[nArgs] = action_args + mark;
		arg_map[nArgs] = literal_map + mark;

		is_expression = all_args_are_expressions; // This would be false for goto/gosub/break/continue.
[ABOVE EDIT END]

[EDIT]
		//==============================
		//jeeswg edit: code inserted
		// The following implements the "% " prefix as a means of forcing an expression:
		if (is_expression
		//&& aActionType == ACT_LOOP
		&& (aActionType == ACT_LOOP || aActionType == ACT_LOOP_FILE || aActionType == ACT_LOOP_PARSE || aActionType == ACT_LOOP_READ || aActionType == ACT_LOOP_REG)
		&& *arg[nArgs] == g_DerefChar && !*arg_map[nArgs] // It's a non-literal deref character.
		//&& *arg[nArgs] == 'x' && !*arg_map[nArgs] // It's a non-literal deref character.
		&& IS_SPACE_OR_TAB(arg[nArgs][1])) // Followed by a space or tab.
		{
			//Beep(3000, 300);
			// Skip the "% " prefix.
			arg[nArgs] = action_args + mark + 2;
			arg_map[nArgs] = literal_map + mark + 2;
			mark += 2;
			//nArgs += 2;
			//continue;
		}
		//==============================
[EDIT END]

[BELOW EDIT]
		// Find the end of the above arg:
		if (is_expression)
			// Find the next delimiter, taking into account quote marks, parentheses, etc.
			mark = FindExprDelim(action_args, g_delimiter, mark, literal_map);
		else
			// Find the next non-literal delimiter.
			mark = FindTextDelim(action_args, g_delimiter, mark, literal_map);
[BELOW EDIT END]

==================================================
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Return to “C/C++”