## An AutoHotkey Classic, the Dynamic Hotstrings() Function Makes Instant RegEx Replacements Possible—Now, You Can Do Math with Your Hotstrings!

Anyone who reads my blog on a routine basis knows how I love Regular Expressions (RegEx). They make feasible all kinds of capabilities not practical by any other method. While not necessarily easy for a beginner to grasp, RegEx provides a mechanism for matching text when you don’t know exactly which characters you need (wildcards). (That’s why I wrote the book *A Beginner’s Guide to Using Regular Expressions in AutoHotkey.*) Although you may encounter a bit of a learning curve, RegEx gives you the ability to accomplish some pretty fancy tasks. This time I plan to demonstrate a couple of Hotstring techniques that might amaze you—they did me!

While doing research for last week’s blog, I came across an alternative technique for capitalizing the first letter in a sentence. It uses a function called *Hotstrings()* originally written back in the AutoHotkey stone age (started by *polyethene*, Feb 4, 2007, and found on the old archived AutoHotkey forum—not to be confused with recently-new built-in AutoHotkey Hotstring() function). I later discovered the version discussed in this blog in the same archived forum (updated by *Edd*, Sep 9, 2014, the post “RegEx Dynamic Hotstrings“). Other variations of “Dynamic Hotstrings” have appeared since that time but I found that, with the arrival of the new built-in *Hotstring()* function, I didn’t need the additional complexity. I only planned to use the Regular Expressions (RegEx) capability.

Note: Perhaps the name of this function should change to something similar to “RegExHotstrings()”—saving confusion with the build-in AutoHotkey *Hotstring()* function. In fact, for the purposes of this blog (and future reference), I have done just that.

The reply posted by *flyingDman* to the question “Auto Capitalize First Letter of Sentence” put me on the trail to this useful Dynamic RegEx Hotstring function.

### The Special Hotstrings() Function

You don’t need to understand how the *RegExHotstrings()* function works. (I only partially grasp what it does.) You only need to include it in your script or make it available in one of your libraries. (I plan to address function libraries soon.) Suffice it to say, the function appears to monitor all of the keys on your keyboard by turning each into an individual Hotkey looking for RegEx matches (similar to a keylogger). As the user types, the function watches the keystrokes. When the function finds a match, it fires a text replacement or subroutine.

I could only find a few examples of how to use the function, so it took me a good bit of fooling around to determine what works and what doesn’t. After reading this blog, you should understand how to make *RegExHotstrings()* work for your scripts.

Note: Others may have written even more versions of this RegExHotstrings() function with better features, but (other than the one I mentioned previously) I don’t know where to find them.

### Capitalize Sentences with RegExHotstrings()

In the original answer to the sentence-capitalization question, *flyingDman* used the *Hotstrings()* function in the following manner:

Hotstrings("\.\s*(\w)", "Label") Return Label: Stringupper, $1, $1 Sendinput, % ". " $1 return

The Regular Expression appears in the first parameter and the subroutine *Label* fills in the second parameter. (If AutoHotkey doesn’t find a matching *Label* in the script, it uses the second parameter as replacement text.)

The RegEx breaks down as follows:

- The
*\.*expression represents the period at the end of a sentence. Normally, the dot acts as a wildcard for any character. Placing the backslash in front of it changes it to a mere dot. - The
*\s*matches any space, tab or any other single whitespace character (i.e. newlines). - The asterisk * tells the RegEx engine to continue matching none or more occurrences of the preceding
*\s*. I changed this to the plus sign*+*(one or more) since the asterisk capitalized file extensions (i.e. RegExHotstrings.Ahk). Using the*\s+*allows for multiple spaces between the period and the first letter of the next sentence, although the subroutine only returns one space. - The
*\w*looks for any letter or digit. Putting a set of parentheses around the expression*(\w)*saves the match as a subpattern for later use in the results variable*$1*.

Making that one correction and changing the name of the function yields:

```
RegExHotstrings("\.\s+(\w)", "Label")
Label:
Stringupper, $1, $1
Sendinput, % ". " $1
Return
```

The *Label* subroutine uses the subpattern result *$1* to convert the first letter of the sentence into uppercase, then executes the SendInput command—inserting the replacement results (a period, one space, and the uppercase letter) into the document or text editing field.

This RegEx tolerates multiple spaces between the period and the first letter of the sentence. Plus, as shown in the download RegExHotstringsApp.ahk script, I easily add the question mark and exclamation point to the capitalization RegEx by replacing *\.* with *(\.|\?|!)*. (This RegEx implementation matches any one of the three sentence terminators. The script requires adjustments in the original subroutine since now it employs both $1 and $2.)

In my last blog, you might complain about the over 100 Hotstrings generated by the built-in AutoHotkey Hotstring() function loop. On the other hand, this *RegExHotstrings()* function turns every key on your keyboard into a Hotkey. I can’t say which I prefer or if it makes much of a difference—except small adjustments to the RegEx in *RegExHotstrings()* can add significantly more Hotstring-like actions to a single function call.

The opportunities for creating unique dynamic Hotstrings with *RegExHotstrings()*—starting with the next example for converting a fraction into a percentage on-the-fly—intrigue me most.

### A Truly Dynamic Hotstring for Calculating Percentages

The RegExHotstringsApp.ahk script posted at the Free AutoHotkey Scripts page contains both the *RegExHotstrings()* function and a number of examples. Most of the examples execute text replacements—which you can often replace with the built-in AutoHotkey *Hotstring()* function and the *X* option (for running functions and subroutines). However, calculating percentages with standard Hotstrings would present a challenge.

This next function call for calculating percentages from raw numbers immediately caught my attention:

RegExHotstrings("(\d+)/(\d+)%", "Percent") ; try: 4/50% Percent: p := Round($1 / $2 * 100,1) SendInput, %p%`% Return

The Regular Expressions above contains the following components:

- The two
*\d+*expressions match one or more numeric digits (0-9). - The
*/*and*%*match a single forward-slash ( / ) and a single percent sign ( % ), respectively. You’ll find some non-AutoHotkey RegEx references (including the original example here) place a backslash in front of the forward-slash ( \/ ). You don’t need the backslash here since the forward-slash holds no special meaning in AutoHotkey Regular Expressions. - The two sets of parentheses (…) save the subpattern results to the variables $1 and $2, respectively. AutoHotkey uses these for calculating the sum in the
*Percent*subroutine.

For example, after typing the character string “345/5673%” and hitting the % key, a match occurs. The subroutine *Percent* dynamically replaces the original text with the calculated “6.1%” string:

345/5673% ⇒ 6.1%

This means we can add percentages directly to documents without first breaking out a calculator.

### Addition and Subtraction Hotstrings

Since I wanted to demonstrate at least one additional use for this Dynamic RegEx Hotstring technique, I chose to write a routine to add/subtract values in a text string. First, I used Ryan’s RegEx Tester to write an expression that recognizes an addition/subtraction string:

RegExHotstrings("(\d+(\.\d+)?(\+|-)(\d+(\.\d+)?(\+|-))*\d+(\.\d+)?)=" ,"Add") ; add/subtract

Not as complicated as it looks!

While this RegEx uses *\d+* in the same manner as the last example for matching any series of numeric digits, you see a few more needed additions:

- The expression
*(\.\d+)?*looks for any decimal point followed by one or more numerical digits. The question mark (*?*) makes the match optional—it may or may not appear. This makes it possible to mix both integers and floating-point numbers in the same string.

Note: If you would like the previous percent calculator to accept non-integer values (containing a decimal point) insert*(?:\.\d+)?*—including parentheses—just after each*\d+*expression (inside parentheses). The modifier*?:*prevents the new sets of parentheses from assigning a new subpattern variable, thwarting the possible misalignment of*$1*and*$2*with the new values. You can find this form of the RegEx in the RegExHotstringsApp.ahk script. - The expression
*(\+|-)*matches either the plus sign (escaped*\+*) or the minus sign ( – ). The vertical bar ( | ) acts like the logical “or” in expressions. - The asterisk ( * ) tells the RegEx engine to continue matching until it reaches the last number followed by a + or – sign—then include the last number
- The entire match activates by pressing the equal sign (
*=*)—the last character for the matching RegEx.

This time the parentheses capture the total addition/subtraction string (without the equals sign) as the first subpattern value *$1* (e.g. “23+34+56-78+90”). Unlike the built-in AutoHotkey RegExReplace() function, when using the *RegExHotstrings()* function $0 does not yield the entire match. Rather, you must use *$1*, *$2*, *$3*, … for consecutive sets of parentheses. In other words, when capturing data for your subroutines, don’t try to get too fancy. (I did not find anything documenting this nuance.)

I wrote the following *Add* subroutine to parse the string (*$1*) and calculate the sum:

Add: AddArray := StrSplit($1 ,"+") Total := 0 Loop % AddArray.MaxIndex() { If InStr(AddArray[A_Index],"-") { SubArray := StrSplit(AddArray[A_Index],"-") SubTotal := SubArray[1] Loop % SubArray.MaxIndex()-1 { SubTotal := SubTotal - SubArray[A_Index+1] } AddArray[A_Index] := SubTotal } Total := Total + AddArray[A_Index] } SendInput, % Format("{:g}", Total) Return

Type a row of addition/subtraction numbers with the appropriate intervening signs (no spaces), then hit the equal sign ( = ). It replaces the string with the calculated total:

`23+34+56-78+90= replaced with ⇒ 125`

This routine only calculates addition and subtraction. While I could probably write a more complicated routine to include multiplication and division, a number of years ago *Pulover* of Pulover’s Macro Creator posted the Eval() function which does all these math calculations (and much more). I plan to demonstrate how to use *Eval()* in next week’s tip.

Include the *RegExHotstrings()* function in your script or one of the AutoHotkey function libraries. This function appears in both the RegExHotstringsApp.ahk script and separately as RegExHotstrings.ahk function file found at the Free AutoHotkey Scripts page:

RegExHotstrings(k, a = "", Options:="") { static z, m = "~$", m_ = "*~$", s, t, w = 2000, sd, d = "Left,Right,Up,Down,Home,End,RButton,LButton", f = "!,+,^,#", f_="{,}" global $ If z = ; init { RegRead, sd, HKCU, Control Panel\International, sDecimal Loop, 94 { c := Chr(A_Index + 32) If A_Index between 33 and 58 Hotkey, %m_%%c%, __hs else If A_Index not between 65 and 90 Hotkey, %m%%c%, __hs } e = 0,1,2,3,4,5,6,7,8,9,Dot,Div,Mult,Add,Sub,Enter Loop, Parse, e, `, Hotkey, %m%Numpad%A_LoopField%, __hs e = BS,Shift,Space,Enter,Return,Tab,%d% Loop, Parse, e, `, Hotkey, %m%%A_LoopField%, __hs z = 1 } If (a == "" and k == "") ; poll { q:=RegExReplace(A_ThisHotkey, "\*\~\$(.*)", "$1") q:=RegExReplace(q, "\~\$(.*)", "$1") If q = BS { If (SubStr(s, 0) != "}") StringTrimRight, s, s, 1 } Else If q in %d% s = Else { If q = Shift return Else If q = Space q := " " Else If q = Tab q := "`t" Else If q in Enter,Return,NumpadEnter q := "`n" Else If (RegExMatch(q, "Numpad(.+)", n)) { q := n1 == "Div" ? "/" : n1 == "Mult" ? "*" : n1 == "Add" ? "+" : n1 == "Sub" ? "-" : n1 == "Dot" ? sd : "" If n1 is digit q = %n1% } Else If (GetKeyState("Shift") ^ !GetKeyState("CapsLock", "T")) StringLower, q, q s .= q } Loop, Parse, t, `n ; check { StringSplit, x, A_LoopField, `r If (RegExMatch(s, x1 . "$", $)) ; match { StringLen, l, $ StringTrimRight, s, s, l if !(x3~="i)\bNB\b") ; if No Backspce "NB" SendInput, {BS %l%} If (IsLabel(x2)) Gosub, %x2% Else { Transform, x0, Deref, %x2% Loop, Parse, f_, `, StringReplace, x0, x0, %A_LoopField%, ¥%A_LoopField%¥, All Loop, Parse, f_, `, StringReplace, x0, x0, ¥%A_LoopField%¥, {%A_LoopField%}, All Loop, Parse, f, `, StringReplace, x0, x0, %A_LoopField%, {%A_LoopField%}, All SendInput, %x0% } } } If (StrLen(s) > w) StringTrimLeft, s, s, w // 2 } Else ; assert { StringReplace, k, k, `n, \n, All ; normalize StringReplace, k, k, `r, \r, All Loop, Parse, t, `n { l = %A_LoopField% If (SubStr(l, 1, InStr(l, "`r") - 1) == k) StringReplace, t, t, `n%l% } If a != t = %t%`n%k%`r%a%`r%Options% } Return __hs: ; event RegExHotstrings("", "", Options) Return }

*Click the Follow button at the top of the sidebar*

*on the right*

*of this page for e-mail notification of new blogs. (If you’re reading this on a tablet or your phone, then you must scroll all the way to the end of the blog—pass any comments—to find the*

**Follow**button.)*This post was proofread by **Grammarly*

(Any other mistakes are all mine.)

(Full disclosure: If you sign up for a free Grammarly account, I get 20¢. I use the spelling/grammar checking service all the time, but, then again, I write a lot more than most people. I recommend Grammarly because it works and it’s free.)

Find my AutoHotkey books at ComputorEdge E-Books!

Buy Jack a Cappuccino!

If you found a blog particularly useful, show your appreciation by buying Jack a cup of coffee! Or, if you prefer something for yourself, check out his books.

$4.95

[…] example, the simple question about capitalizing sentences led me to the RegExHotstrings() function discussed last time. As I dug deeper into the math-side of dynamic Hotstrings, I discovered the […]

LikeLike

[…] the past two blogs, “Dynamic Regular Expressions (RegEx) for Math Calculating Hotstrings” and “The Eval() Function for Hotkey Math Calculations and Text Manipulation“, I […]

LikeLike

[…] the RegExHotstrings() function has its limitations (discussed in “Dynamic Regular Expressions (RegEx) for Math Calculating Hotstrings“), we can quickly implement some simple (yet complex) dynamic Hotstrings using a one-line […]

LikeLike

Thank you for this!

it any chance upgrade this function for non english characters too?

if i have characters like “ščřžý” but script can not hadlle it. Even if I add (*UCP)

like here:

;text is “Lukáš a Jindřich ”

RegExHotstrings(“(*UCP)(\w+ )(a|i|o|u)[ ](\w+ )”, “replaceMy”)

return

replaceMy:

ifWinActive, ahk_class illustrator

SendInput,%$1%%$2%{space}%$3%

return

result: LukLuk a Jindich

Non English characters are ommited

here the same:

RegExHotstrings(“(*UCP)ččč”, “test”) ; nothing happend:/

I have file encoding UTF-8-raw and classic regex work fine with *UCP

Any ideas? thank you very much!

LikeLike

I think your best bet is to post your question on the AutoHotkey forum—similar to this post:

“RegExHotstrings without removing the matched text?”

I didn’t write the function so I only have a limited understanding of how it works. Many forum users are much smarter than me.

LikeLike

It works for me when not including any of my other libs/functions, but as soon as I include any other file with hotstrings and/or hotkeys it stops working, no longer recognizing or triggering the regex hotstrings.

Any clues what could interfere with the RegExHotstrings function?

LikeLike