A preprocessor adding JS style template literals, anonymous functions + closures, code continuation, indent-based blocks

Post your working scripts, libraries and tools for AHK v1.1 and older
20170201225639
Posts: 144
Joined: 01 Feb 2017, 22:57

A preprocessor adding JS style template literals, anonymous functions + closures, code continuation, indent-based blocks

Post by 20170201225639 » 04 Oct 2021, 23:39

Examples:

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)
		}


}
Note that the "anonymous functions" have been concatnated to the end of the output string (the approach is based on the lexikos' ideas here viewtopic.php?t=10784&p=59559#p59559)



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 🔵
}

Attachments
test.ahk
(10.75 KiB) Downloaded 37 times
Last edited by 20170201225639 on 06 Oct 2021, 02:25, edited 6 times in total.

tuzi
Posts: 223
Joined: 27 Apr 2016, 23:40

Re: A preprocessor adding JS style template literals, anonymous functions + closures, code continuation, indent-based bl

Post by tuzi » 05 Oct 2021, 06:25

I see a lot of symbols like emoji in your code, so the example doesn't work.

20170201225639
Posts: 144
Joined: 01 Feb 2017, 22:57

Re: A preprocessor adding JS style template literals, anonymous functions + closures, code continuation, indent-based bl

Post by 20170201225639 » 05 Oct 2021, 08:06

Hi tuzi, if you save the example as "example.ahk-unprocessed", and run the following script in the same directory, does it set your clipboard to be the output string (the valid AHK code)?

Can you normally use unicode symbols in identifiers in your AutoHotkey version? There were some versions where you have to careful about saving your file with a byte order mark, but I think in recent versions that's no longer the case.


Code: Select all


#NoEnv
FileEncoding, UTF-8

FileRead, source, example.ahk-unprocessed

target := BrPr(source)

clipboard := target



BrPr(InputStr){
	return (BrPr_LoopMatches(InputStr, "▛([^▛]+?)▟", {MatchReplacer: Func("BrPr_SqrBrkts"), GetFinalOutputStr: 1, Recursive: 1})).FinalOutputStr . "`r`n" . BrPr_AnonFuncs()
}

BrPr_SqrBrkts(🍡){ ;  ▛●▟ => f(●)
	If not IsObject(🍡) ; for testing, call 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 (🅾=="S" or 🅾=="SJ"){
		if InStr(🔵, "`n")
			🔵 := RegExReplace(🔵, "^[ \t]*\R+|\R+[ \t]*$")
		else
			🔵 := BrPr_TrimCRLFSpaceTab(🔵)
		if (🅾=="S")
			🟨 := ["StrReplace", [["""", """"""], ["`r", "``r"], ["`n", "``n"]]]
		else if (🅾=="SJ")
			🟨 := ["Regex", [["""", """"""],["\s*\n\s*", A_Space], ["^\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_ComputeReplacement(🟡, 🔵){
	if IsFunc(🟡)
		🟢 := 🟡.Call(🔵)
	else
	{
		🟢 := (🔵.IsMatch)?(🔵.Occurrence.Value):🔵
		if (🟡[1]=="Regex")
			Loop % 🟡[2].Length()
			🟢 := 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_DebuiltIn(🟠){
	🔵 := Object()
	Loop % (🟠.Count()+1) {
		🟡:= A_Index-1, 🟢 := {Name: 🟠.Name(🟡) , Value: 🟠.Value(🟡) , Len: 🟠.Len(🟡) , Pos: 🟠.Pos(🟡) }
		🟡? (🔵.Groups.Push(🟢)):(🔵 := 🟢, 🔵.Groups := [])
	}
	return 🔵
}

BrPr_LoopMatches(🟡, 🆁, 🔵:=""){
	🏀 := {InputStr: 🟡, FinalOutputStr: "", Passes: []} ; object (ball) to be returned
	🆁 := "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(🟠)}
			if (🔵.MatchReplacer)
				📦[👉].Replacement := BrPr_ComputeReplacement(🔵.MatchReplacer, 📦[👉] )
			🏃‍♀️ := 🟠.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_ComputeReplacement(🔵.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 🔵
}

tuzi
Posts: 223
Joined: 27 Apr 2016, 23:40

Re: A preprocessor adding JS style template literals, anonymous functions + closures, code continuation, indent-based bl

Post by tuzi » 06 Oct 2021, 01:10

20170201225639 wrote:
05 Oct 2021, 08:06
Hi tuzi, if you save the example as "example.ahk-unprocessed", and run the following script in the same directory, does it set your clipboard to be the output string (the valid AHK code)?

Can you normally use unicode symbols in identifiers in your AutoHotkey version? There were some versions where you have to careful about saving your file with a byte order mark, but I think in recent versions that's no longer the case.


Code: Select all


#NoEnv
FileEncoding, UTF-8

FileRead, source, example.ahk-unprocessed

target := BrPr(source)

clipboard := target



BrPr(InputStr){
	return (BrPr_LoopMatches(InputStr, "▛([^▛]+?)▟", {MatchReplacer: Func("BrPr_SqrBrkts"), GetFinalOutputStr: 1, Recursive: 1})).FinalOutputStr . "`r`n" . BrPr_AnonFuncs()
}

BrPr_SqrBrkts(🍡){ ;  ▛●▟ => f(●)
	If not IsObject(🍡) ; for testing, call 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 (🅾=="S" or 🅾=="SJ"){
		if InStr(🔵, "`n")
			🔵 := RegExReplace(🔵, "^[ \t]*\R+|\R+[ \t]*$")
		else
			🔵 := BrPr_TrimCRLFSpaceTab(🔵)
		if (🅾=="S")
			🟨 := ["StrReplace", [["""", """"""], ["`r", "``r"], ["`n", "``n"]]]
		else if (🅾=="SJ")
			🟨 := ["Regex", [["""", """"""],["\s*\n\s*", A_Space], ["^\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_ComputeReplacement(🟡, 🔵){
	if IsFunc(🟡)
		🟢 := 🟡.Call(🔵)
	else
	{
		🟢 := (🔵.IsMatch)?(🔵.Occurrence.Value):🔵
		if (🟡[1]=="Regex")
			Loop % 🟡[2].Length()
			🟢 := 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_DebuiltIn(🟠){
	🔵 := Object()
	Loop % (🟠.Count()+1) {
		🟡:= A_Index-1, 🟢 := {Name: 🟠.Name(🟡) , Value: 🟠.Value(🟡) , Len: 🟠.Len(🟡) , Pos: 🟠.Pos(🟡) }
		🟡? (🔵.Groups.Push(🟢)):(🔵 := 🟢, 🔵.Groups := [])
	}
	return 🔵
}

BrPr_LoopMatches(🟡, 🆁, 🔵:=""){
	🏀 := {InputStr: 🟡, FinalOutputStr: "", Passes: []} ; object (ball) to be returned
	🆁 := "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(🟠)}
			if (🔵.MatchReplacer)
				📦[👉].Replacement := BrPr_ComputeReplacement(🔵.MatchReplacer, 📦[👉] )
			🏃‍♀️ := 🟠.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_ComputeReplacement(🔵.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 🔵
}
my clipboard only have something like whitespace characters.

20170201225639
Posts: 144
Joined: 01 Feb 2017, 22:57

Re: A preprocessor adding JS style template literals, anonymous functions + closures, code continuation, indent-based bl

Post by 20170201225639 » 06 Oct 2021, 01:58

I figured it out, it's absolutely necessary to save the file with a byte order mark (i.e. as UTF-8 (with BOM) not UFT-8 RAW (w/o BOM))

I've attached a file (test.ahk) already saved with BOM. If you run it, it will read the examples I posted above (embedded in it in the continuation section at the top) & generate a transpiled .ahk file in the same folder, and run it. If you add "#Persistent" at the top of test.ahk, you can edit the continuation section in test.ahk, reload test.ahk, and it will recompile-reload the transpiled version again, so it's possible to play with it and instantly see the results.



tuzi wrote:
06 Oct 2021, 01:10
20170201225639 wrote:
05 Oct 2021, 08:06
Hi tuzi, if you save the example as "example.ahk-unprocessed", and run the following script in the same directory, does it set your clipboard to be the output string (the valid AHK code)?

Can you normally use unicode symbols in in your AutoHotkey version? There were some versions where you have to careful about saving your file with a byte order mark, but I think in recent versions that's no longer the case.


Code: Select all


#NoEnv
FileEncoding, UTF-8

FileRead, source, example.ahk-unprocessed

target := BrPr(source)

clipboard := target



BrPr(InputStr){
	return (BrPr_LoopMatches(InputStr, "▛([^▛]+?)▟", {MatchReplacer: Func("BrPr_SqrBrkts"), GetFinalOutputStr: 1, Recursive: 1})).FinalOutputStr . "`r`n" . BrPr_AnonFuncs()
}

BrPr_SqrBrkts(🍡){ ;  ▛●▟ => f(●)
	If not IsObject(🍡) ; for testing, call 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 (🅾=="S" or 🅾=="SJ"){
		if InStr(🔵, "`n")
			🔵 := RegExReplace(🔵, "^[ \t]*\R+|\R+[ \t]*$")
		else
			🔵 := BrPr_TrimCRLFSpaceTab(🔵)
		if (🅾=="S")
			🟨 := ["StrReplace", [["""", """"""], ["`r", "``r"], ["`n", "``n"]]]
		else if (🅾=="SJ")
			🟨 := ["Regex", [["""", """"""],["\s*\n\s*", A_Space], ["^\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_ComputeReplacement(🟡, 🔵){
	if IsFunc(🟡)
		🟢 := 🟡.Call(🔵)
	else
	{
		🟢 := (🔵.IsMatch)?(🔵.Occurrence.Value):🔵
		if (🟡[1]=="Regex")
			Loop % 🟡[2].Length()
			🟢 := 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_DebuiltIn(🟠){
	🔵 := Object()
	Loop % (🟠.Count()+1) {
		🟡:= A_Index-1, 🟢 := {Name: 🟠.Name(🟡) , Value: 🟠.Value(🟡) , Len: 🟠.Len(🟡) , Pos: 🟠.Pos(🟡) }
		🟡? (🔵.Groups.Push(🟢)):(🔵 := 🟢, 🔵.Groups := [])
	}
	return 🔵
}

BrPr_LoopMatches(🟡, 🆁, 🔵:=""){
	🏀 := {InputStr: 🟡, FinalOutputStr: "", Passes: []} ; object (ball) to be returned
	🆁 := "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(🟠)}
			if (🔵.MatchReplacer)
				📦[👉].Replacement := BrPr_ComputeReplacement(🔵.MatchReplacer, 📦[👉] )
			🏃‍♀️ := 🟠.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_ComputeReplacement(🔵.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 🔵
}
my clipboard only have something like whitespace characters.
Attachments
test.ahk
(10.75 KiB) Downloaded 37 times

User avatar
Cerberus
Posts: 172
Joined: 12 Jan 2016, 15:46

Re: A preprocessor adding JS style template literals, anonymous functions + closures, code continuation, indent-based bl

Post by Cerberus » 10 Oct 2021, 16:39

This is a rather cool idea! Will try it out in the future. Anonymous functions are highly practical.

tuzi
Posts: 223
Joined: 27 Apr 2016, 23:40

Re: A preprocessor adding JS style template literals, anonymous functions + closures, code continuation, indent-based bl

Post by tuzi » 14 Oct 2021, 07:25

It works fine now.

I think the problem before was that I missed the example.ahk-unprocessed

:thumbup: :thumbup:

Post Reply

Return to “Scripts and Functions (v1)”