Code: Select all
Msgbox % (str_A := ▛ 1. no need to escape "quotes" ▟)
Msgbox % (str_B := ▛ 2. js style template literal, like this: ${str_A} ▟)
Msgbox % (str_C := ▛
3. strings can span
multiple lines
▟)
▛(F) Msgbox % "4. one-line anonymous function" ▟.Call()
Fn := ▛(F)(param) return % "5. one-line anonymous function with param (= " . param . ")" ▟
Msgbox % Fn.Call(str_A)
▛(F)(param) Msgbox % ▛
6. A one-line anonymous function with param
referenced inside a template literal embedded expression
(=${param})
▟ ▟.Call(str_A)
Fn := ▛(F) ; anonymous functions can span multiple lines, & you can write comments in them
; "closure" implemeneted following Lexikos' idea here
; https://www.autohotkey.com/boards/viewtopic.php?t=10784&p=59559#p59559
; but with this limitation: bound variables must be explicitly indicated by surrounding them with a pair of brackets
divisor := 5
DivideByFive := ▛(F)([divisor], dividend)
return % (dividend / divisor) ▟
divisor := 10
DivideByTen := ▛(F)([divisor], dividend)
return % (dividend / divisor) ▟
Msgbox % DivideByTen.Call(50) ; 5.0000
Msgbox % DivideByFive.Call(50) ; 10.0000
dividend := 999
Divide999ByTen := ▛(F)([dividend], [DivideByTen]) return DivideByTen.Call(dividend) ▟
Msgbox % Divide999ByTen .Call() ; 99.9000
▟.Call()
Obj := {▛(J)
key1: "Join mode (J) is useful for defining an object over multiple lines",
key2:
"(J) means all lines between the brackets
will be trimmed from both ends
& collapsed into one single line ...
joined by a space character",
key3:
"so Join mode (J) doesn't automatically supply commas",
key4:
▛ By contrast, (J,) mode, i.e. "Join-by-comma" mode is like (J) except the lines are joined by a comma.
See the example below. ▟,
key5:
[▛(J,)
123
239
1230
123
123
▟]
▟}
Msgbox % ▛
when debugging, it may be tiresome to write, e.g.
A_LineFile = ${A_LineFile}
so a shortcut for that is:
${(A_LineFile)}
i.e. just put a pair of parentheses around the expression
▟
Fn := ▛(F)(number)
; Indent mode (I) ("Python mode") removes the need for brackets { }
; only indenting by tabs is supported currently
▛(I)
five := 5
ten := 10
if (number>five)
if (number<ten)
Msgbox % ▛ ❶ ${(number)} between ${five} and ${ten} ▟
else if (number>100)
if (number>200)
if (number>300)
if (number>400)
Msgbox % ▛ ❷ big number! ${(number)} ▟
else
Msgbox % ▛ ❸ small ${(number)} ▟
▟
▟
Fn.Call(8) ; ❶
Fn.Call(408) ; ❷
Fn.Call(3) ; ❸
; (SJ) is like (J) except for strings. This is useful for listing e.g. command line parameters over multiple lines
; embedded ${expressions} work here as well
Fn := "RunCMD"
file := ▛ C:\zip.zip ▟
Outfolder := ▛ C:\temp ▟
If (f:=Func("RunCMD"))
Msgbox % f.call( ▛(SJ)
${A_ComSpec A_Space}
/c
ftype
AutoHotkeyScript
▟ )
The above "transpiles" to:
Code: Select all
Msgbox % (str_A := "1. no need to escape ""quotes""")
Msgbox % (str_B := "2. js style template literal, like this: " . (str_A) )
Msgbox % (str_C := " 3. strings can span`r`n multiple lines")
Func("BrPr_AnonFunc_1").Call()
Fn := Func("BrPr_AnonFunc_2")
Msgbox % Fn.Call(str_A)
Func("BrPr_AnonFunc_6").Call(str_A)
Fn := Func("BrPr_AnonFunc_7").Call()
Obj := {key1: "Join mode (J) is useful for defining an object over multiple lines", key2: "(J) means all lines between the brackets will be trimmed from both ends & collapsed into one single line ... joined by a space character", key3: "so Join mode (J) doesn't automatically supply commas", key4: " By contrast, (J,) mode, i.e. ""Join-by-comma"" mode is like (J) except the lines are joined by a comma.`r`n See the example below. ", key5: [123, 239, 1230, 123, 123]}
Msgbox % " when debugging, it may be tiresome to write, e.g.`r`n A_LineFile = " . (A_LineFile) . "`r`n so a shortcut for that is:`r`n A_LineFile = " . (A_LineFile) . "`r`n i.e. just put a pair of parentheses around the expression"
Fn := Func("BrPr_AnonFunc_8")
Fn.Call(8) ; ❶
Fn.Call(408) ; ❷
Fn.Call(3) ; ❸
; (SJ) is like (J) except for strings. This is useful for listing e.g. command line parameters over multiple lines
; embedded ${expressions} work here as well
Fn := "RunCMD"
file := "C:\zip.zip"
Outfolder := "C:\temp"
If (f:=Func("RunCMD"))
Msgbox % f.call( (A_ComSpec A_Space) . "/c ftype AutoHotkeyScript" )
BrPr_AnonFunc_1(){
Msgbox % "4. one-line anonymous function"
}
BrPr_AnonFunc_2(param){
return % "5. one-line anonymous function with param (= " . param . ")"
}
BrPr_AnonFunc_3(env, dividend){
divisor := env.divisor
return % (dividend / divisor)
}
BrPr_AnonFunc_4(env, dividend){
divisor := env.divisor
return % (dividend / divisor)
}
BrPr_AnonFunc_5(env){
dividend := env.dividend, DivideByTen := env.DivideByTen
return DivideByTen.Call(dividend)
}
BrPr_AnonFunc_6(param){
Msgbox % " 6. A one-line anonymous function with param`r`n referenced inside a template literal embedded expression`r`n (=" . (param) . ")"
}
BrPr_AnonFunc_7(){
; anonymous functions can span multiple lines, & you can write comments in them
; "closure" implemeneted following Lexikos' idea here
; https://www.autohotkey.com/boards/viewtopic.php?t=10784&p=59559#p59559
; but with this limitation: bound variables must be explicitly indicated by surrounding them with a pair of brackets
divisor := 5
DivideByFive := Func("BrPr_AnonFunc_3").Bind({divisor: divisor})
divisor := 10
DivideByTen := Func("BrPr_AnonFunc_4").Bind({divisor: divisor})
Msgbox % DivideByTen.Call(50) ; 5.0000
Msgbox % DivideByFive.Call(50) ; 10.0000
dividend := 999
Divide999ByTen := Func("BrPr_AnonFunc_5").Bind({dividend: dividend, DivideByTen: DivideByTen})
Msgbox % Divide999ByTen .Call() ; 99.9000
}
BrPr_AnonFunc_8(number){
; Indent mode (I) ("Python mode") removes the need for brackets { }
; only indenting by tabs is supported currently
five := 5
ten := 10
if (number>five)
{
if (number<ten)
{
Msgbox % "❶ number = " . (number) . " between " . (five) . " and " . (ten)
}
else if (number>100)
{
if (number>200)
{
if (number>300)
{
if (number>400)
{
Msgbox % "❷ big number! number = " . (number)
}
}
}
}
}
else
{
Msgbox % "❸ small number = " . (number)
}
}
The function is here, just pass it the content of an ahk file with these brackets (and hence is not valid ahk), and it will return the transpiled version that AHK interpreter s.
The workflow I follow is, I gather all my files containing these brackets (which I put in a special folder), concatenate all of them, then pass that big string to BrPr, and save the returned str to an .ahk file (in my case, main.ahk) outside of that folder, which I run as my main AHK persistent script. When I have edited some of the files in that folder, instead of directly reloading, I press a key that will first "recompile" main.ahk from all the files in that folder, then reload. I've optimized the functions as much as I to, and the whole recompile-reload process (the final concatenated file is about 249kb, and I use these brackets almost every other line) typically takes less than half a second (though the indent mode probably needs to be rewritten, but I don't use it very much)
Code: Select all
FileEncoding, UTF-8
MetaLanguage =
(%
Msgbox % (str_A := ▛ 1. no need to escape "quotes" ▟)
Msgbox % (str_B := ▛ 2. js style template literal, like this: ${str_A} ▟)
Msgbox % (str_C := ▛
3. strings can span
multiple lines
▟)
▛(F) Msgbox % "4. one-line anonymous function" ▟.Call()
Fn := ▛(F)(param) return % "5. one-line anonymous function with param (= " . param . ")" ▟
Msgbox % Fn.Call(str_A)
▛(F)(param) Msgbox % ▛
6. A one-line anonymous function with param
referenced inside a template literal embedded expression
(=${param})
▟ ▟.Call(str_A)
Fn := ▛(F) ; anonymous functions can span multiple lines, & you can write comments in them
; "closure" implemeneted following Lexikos' idea here
; https://www.autohotkey.com/boards/viewtopic.php?t=10784&p=59559#p59559
; but with this limitation: bound variables must be explicitly indicated by surrounding them with a pair of brackets
divisor := 5
DivideByFive := ▛(F)([divisor], dividend)
return % (dividend / divisor) ▟
divisor := 10
DivideByTen := ▛(F)([divisor], dividend)
return % (dividend / divisor) ▟
Msgbox % DivideByTen.Call(50) ; 5.0000
Msgbox % DivideByFive.Call(50) ; 10.0000
dividend := 999
Divide999ByTen := ▛(F)([dividend], [DivideByTen]) return DivideByTen.Call(dividend) ▟
Msgbox % Divide999ByTen .Call() ; 99.9000
▟.Call()
Obj := {▛(J)
key1: "Join mode (J) is useful for defining an object over multiple lines",
key2:
"(J) means all lines between the brackets
will be trimmed from both ends
& collapsed into one single line ...
joined by a space character",
key3:
"so Join mode (J) doesn't automatically supply commas",
key4:
▛ By contrast, (J,) mode, i.e. "Join-by-comma" mode is like (J) except the lines are joined by a comma.
See the example below. ▟,
key5:
[▛(J,)
123
239
1230
123
123
▟]
▟}
Msgbox % ▛
when debugging, it may be tiresome to write, e.g.
A_LineFile = ${A_LineFile}
so a shortcut for that is:
${(A_LineFile)}
i.e. just put a pair of parentheses around the expression
▟
Fn := ▛(F)(number)
; Indent mode (I) ("Python mode") removes the need for brackets { }
; only indenting by tabs is supported currently
▛(I)
five := 5
ten := 10
if (number>five)
if (number<ten)
Msgbox % ▛ ❶ ${(number)} between ${five} and ${ten} ▟
else if (number>100)
if (number>200)
if (number>300)
if (number>400)
Msgbox % ▛ ❷ big number! ${(number)} ▟
else
Msgbox % ▛ ❸ small ${(number)} ▟
▟
▟
Fn.Call(8) ; ❶
Fn.Call(408) ; ❷
Fn.Call(3) ; ❸
; (SJ) is like (J) except for strings. This is useful for listing e.g. command line parameters over multiple lines
; embedded ${expressions} work here as well
Fn := "RunCMD"
file := ▛ C:\zip.zip ▟
Outfolder := ▛ C:\temp ▟
If (f:=Func("RunCMD"))
Msgbox % f.call( ▛(SJ)
${A_ComSpec A_Space}
/c
ftype
AutoHotkeyScript
▟ )
)
ObjLanguage := BrPr(MetaLanguage)
fp = %A_ScriptDir%/Compiled_%A_TickCount%.ahk
FileAppend, % ObjLanguage, %fp%,
Sleep, 100
Run % fp
return
BrPr(InputStr){
🆁 := "▛([^▛]+?)▟"
🍡 := BrPr_LoopMatches(InputStr, 🆁, {MatchReplacer: Func("BrPr_SqrBrkts"), GetFinalOutputStr: 1, Recursive: 1})
return 🍡.FinalOutputStr . "`r`n" . BrPr_AnonFuncs()
}
BrPr_SqrBrkts(🍡){ ; ▛●▟ => f(●)
if not IsObject(🍡) ; for testing, pass a string to this func to see what it returns
🔵 := 🍡
else
🔵 := 🍡.Occurrence.Groups.1.Value
if RegExMatch(🔵, "O)^\(([^\n\r\(]+?)\)([\s\S]+)$", 🔥)
🅾 := 🔥.Value(1), 🔵 := 🔥.Value(2)
else
🅾 := "S"
If (SubStr(🅾, 1, 1)=="S"){
if InStr(🔵, "`n")
🔵 := RegExReplace(🔵, "^[ \t]*[\r\n]+|[\r\n]+[ \t]*$")
else
🔵 := BrPr_TrimCRLFSpaceTab(🔵)
if (🅾=="S")
🟨 := ["StrReplace", [["""", """"""], ["`r", "``r"], ["`n", "``n"]]]
else if (🅾=="SJ")
{
; 🟨 := ["Regex", [["""", """"""],["\s*\n\s*", 👉 A_Space 👈 ], ["^\s*|\s*$", ""]]]
🟨 := ["Regex", [["""", """"""],["\s*\n\s*", """ . A_Space . """], ["^\s*|\s*$", ""]]]
}
else if (🅾=="SJNoSpace"){
🟨 := ["Regex", [["""", """"""],["\s*\n\s*", ""], ["^\s*|\s*$", ""]]]
}
🟠 := (BrPr_LoopMatches(🔵 , "\$\{([^\r\n{}]+?)\}" , {MatchReplacer: Func("BrPr_EmbeddedExpr") , SecondaryReplacer: 🟨, GetFinalOutputStr: 1})).FinalOutputStr
🟠 := RegExReplace((🟠 := """" 🟠 """"), "\. """"$|^"""" \. ")
}
else if (🅾=="I")
🟠 := BrPr_Indent(🔵)
else if (🅾=="F")
🟠 := BrPr_AnonFuncs(🔵)
else
for k,v in StrSplit(🅾, " ")
if (SubStr(v, 1,1)=="J"){
Sep := SubStr(v, 2) ; empty str if 🅾 is 'J'
, 🟠 := BrPr_TrimCRLFSpaceTab(🔵)
, 🟠 := RegExReplace(🟠, "\s*\n\s*", Sep A_Space)
, 🟠 := RegExReplace(🟠, "\s*\t\s*", A_Space)
}
return 🟠
}
BrPr_EmbeddedExpr(🍡){ ; ${●} => f(●)
🔵 := 🍡.Occurrence.Groups.1.Value
if (SubStr(🔵, 1, 1)=="(" && SubStr(🔵, 0) ==")")
return Format(" {1} = "" . ({1}) . """, SubStr(🔵, 2, -1))
else
return Format(""" . ({1}) . """, 🔵)
}
BrPr_DebuiltIn(🟠){
🔵 := Object()
Loop % (🟠.Count()+1) {
🟡:= A_Index-1, 🟢 := {Name: 🟠.Name(🟡) , Value: 🟠.Value(🟡) , Len: 🟠.Len(🟡) , Pos: 🟠.Pos(🟡) }
🟡? (🔵.Groups.Push(🟢)):(🔵 := 🟢, 🔵.Groups := [])
}
return 🔵
}
BrPr_ComputerReplacement(🟡, 🔵){
if IsFunc(🟡)
🟢 := 🟡.Call(🔵)
else
{
🟢 := (🔵.IsMatch)?(🔵.Occurrence.Value):🔵
if (🟡[1]=="Regex")
Loop % 🟡[2].Length(){
; __MsgboxFormat__(🟡)
🟢 := RegExReplace(🟢, 🟡[2][A_Index][1], 🟡[2][A_Index][2])
}
else if (🟡[1]=="StrReplace"){
Loop % 🟡[2].Length()
🟢 := StrReplace(🟢, 🟡[2][A_Index][1], 🟡[2][A_Index][2])
}
}
return 🟢
}
BrPr_LoopMatches(🟡, 🆁, 🔵:=""){
🏀 := {InputStr: 🟡, FinalOutputStr: "", Passes: []} ; object (ball) to be returned
if IsObject(🆁)
🆁 := 🆁[1] . ")" . 🆁[2]
else
🆁 := "O)" 🆁
Loop {
🅿 := A_Index ; which full pass is current loop
; do one full pass thru str
🏃♀️ := 1, 📦 := [] ; 🏃♀️ = StartPos , 📦 = matches collected on this pass
Loop {
👉 := A_Index
if not RegExMatch(🟡, 🆁, 🟠, 🏃♀️)
break
📦[👉] := {IsMatch: 1, StartPos: 🏃♀️, Occurrence: BrPr_DebuiltIn(🟠)}
; __MsgboxFormat__( 📦[👉] )
if (🔵.MatchReplacer){
📦[👉].Replacement := BrPr_ComputerReplacement(🔵.MatchReplacer, 📦[👉] )
; __MsgboxFormat__( 📦[👉] )
}
🏃♀️ := 🟠.Pos(0) + 🟠.Len(0)
}
🏀.Passes[🅿] := {Matches: 📦}
if (🔵.GetFinalOutputStr){
🟪 := "" ; intermediate output str on each full pass
PosAfter := 1
Loop % (📦.Length()+1)
{
if (A_Index== (📦.Length()+1))
🟧 := SubStr(🟡, PosAfter) ; if no matches found, this is the entire string
else
🏃 := 📦[A_Index].StartPos
, 🏁 := 📦[A_Index].Occurrence.Pos
, PosAfter := 🏁 + 📦[A_Index].Occurrence.Len
, 🟧 := SubStr(🟡, 🏃, 🏁-🏃)
if 🔵.SecondaryReplacer
🟧 := BrPr_ComputerReplacement(🔵.SecondaryReplacer, 🟧)
🟪 .= 🟧 . 📦[A_Index].Replacement
}
🏀.FinalOutputStr := (🏀.Passes[🅿].IntermediateFinalOutputStr := 🟪)
}
if (🔵.Recursive) {
if not 📦.Length()
break
else
🟡 := 🏀.FinalOutputStr ; jumping back to start of outermost loop on line #3
}
else
break
}
return 🏀
}
BrPr_AnonFuncs(🔵:=""){
static FuncCounter := 1, FuncDefArr := []
if not 🔵
return BrPr_Join(FuncDefArr, "`r`n")
Fn := {Name: "BrPr_AnonFunc_" FuncCounter++}
if not RegExMatch(🔵, "O)^\(([^\r\n\(]+?)\)([\s\S]+)$", 🟠)
Fn.Body := 🔵, Fn.Ref := "Func(""" Fn.Name """)", Fn.Def := Format("`r`n{1}(){`r`n{2}`r`n}", Fn.Name, Fn.Body)
else
{
Fn.Body:= 🟠.Value(2), Fn.Params:= 🟠.Value(1)
, 🟣 := [] ; local vars
, 🟢 := [] ; nonlocal vars
for k, 🅿 in StrSplit(Fn.Params, "," , A_Space A_Tab)
{
if (SubStr(🅿, 1, 1)=="[") && (SubStr(🅿, 0)=="]")
🟢.Push(SubStr(🅿, 2, -1))
else
🟣.Push(🅿)
}
🟪 := BrPr_Join(🟣, ", ")
if not 🟢.Length()
Fn.Ref := Format("Func(""{1}"")", Fn.Name, 🟪)
, Fn.Def := Format("`r`n{1}({2}){`r`n{3}`r`n}", Fn.Name, 🟪, Fn.Body)
else
{
💚 := []
Loop % 🟢.Length()
💚[A_Index] := 🟢[A_Index] . " := env." . 🟢[A_Index]
, 🟢[A_Index] := 🟢[A_Index] . ": " . 🟢[A_Index]
🟩 := BrPr_Join(🟢, ", "), 💚 := BrPr_Join(💚, ", ")
, Fn.Ref := Format("Func(""{1}"").Bind(`{{2}`})", Fn.Name, 🟩)
, 🟪?🟪:=", " 🟪:0
, Fn.Def := Format("`r`n{1}(env{2}){`r`n{4}`r`n{3}`r`n}", Fn.Name, 🟪, Fn.Body, 💚)
}
}
FuncDefArr.Push(Fn.Def)
return Fn.Ref
}
BrPr_Join(❶, ❷:=""){
for k, v in ❶
❸ .= ❷ . v
return SubStr(❸, 1+StrLen(❷))
}
BrPr_TrimCRLFSpaceTab(❶){
return Trim(❶, "`n`r`t" A_Space)
}
BrPr_Indent(🔵){
🔵 := RegExReplace(🔵, "^\s*\r?\n|\r?\n\s*$")
🍡 := BrPr_StripIndent(🔵)
🔵 := 🍡[1], MinNum := 🍡[2]
🔵 := BrPr_CollapseTree( BrPr_Indent2Tree(🔵), -1)
Loop % MinNum
Indent .= A_Tab
🔵 := "`r`n" RegExReplace(🔵, "m)(*ANYCRLF)^(.+)$", Indent "$1")
return 🔵
}
BrPr_StripIndent(Str){
MinNum := -1
Loop Parse, Str, `n, `r
{
NumOfTabs := 0
Line := A_LoopField
Loop Parse, Line
if (A_LoopField!=A_Tab)
break
else
NumOfTabs++
MinNum := (MinNum==-1)?NumOfTabs:(Min(NumOfTabs, MinNum))
}
Str := RegExReplace(Str, "m)(*ANYCRLF)^\t{" MinNum "}", "")
return [Str, MinNum]
}
BrPr_Indent2Tree(Str){
🌳 := []
PrevNumOfTabs := 0
PosArr := [0]
Loop Parse, Str, `n, `r
{
Line := A_LoopField
NumOfTabs := 0
Loop Parse, Line
if (A_LoopField!=A_Tab)
break
else
NumOfTabs++
If (NumOfTabs>PrevNumOfTabs)
Loop % (NumOfTabs-PrevNumOfTabs)
PosArr.Push(1)
else if (NumOfTabs==PrevNumOfTabs)
PosArr[PosArr.Length()]++
else
Loop % (PrevNumOfTabs-NumOfTabs)
PosArr.Pop(), PosArr[PosArr.Length()]++
PrevNumOfTabs := NumOfTabs
🟡 := 🌳
Loop % PosArr.Length() {
X := PosArr[A_Index]
if not (🟡.📦[X])
🟡.📦[X] := []
if (A_Index == PosArr.Length())
🟡.📦[X] := {📄: Trim(Line)}
else
🟡 := 🟡.📦[X]
}
}
return (🌳)
}
BrPr_CollapseTree(🌳, Depth:=0){
NodeContent := 🌳.📄, NodeChildren := 🌳.📦, 🔵 := ""
If (Depth>-1)
{
Loop % (Depth)
Indent .= A_Tab
🔵 := Indent . NodeContent . "`r`n"
}
Loop % NodeChildren.Length()
{
if (Depth>-1) && (A_Index==1)
🔵 .= Indent . "{`r`n"
🔵 .= BrPr_CollapseTree(NodeChildren[A_Index], Depth+1)
if (Depth>-1) && (A_Index==NodeChildren.Length())
🔵 .= Indent . "}`r`n"
}
return 🔵
}