tiny suggest:
(eval_ naming)
include auto init within eval_ funct. (ie. simple function eval_(xxx) works without need for any prep.)
eg:
Code:
; Eval.ahk
; tiny example mods by JD2W
; Various low-level functions for interacting with the internals of AutoHotkey.
; Based on v1.0.47.04.
;
; DISCLAIMER: MAY CRASH OTHER VERSIONS OF AUTOHOTKEY.
;
; Allow auto-inclusion when named "lowlevel.ahk".
; MUST BE CALLED AT LEAST ONCE for Eval_getVar and Eval_static to work.
Eval_init() {
Global Eval_init_done
if Eval_init_done
return
Eval_init_done = 1
; Because Eval_mcode makes Eval_getVar "built-in", Eval_getFirstFunc
; (which makes certain assumptions about the order of functions)
; must be called at least once before Eval_mcode, or it may
; not be able to find Eval_getVar and Eval_static later on.
Eval_getFirstFunc()
Eval_mcode("Eval_getVar","8B4C24088B0933C08379080375028B018B4C2404998901895104C3")
Eval_mcode("Eval_static","8B4424088B008378080375068B0080480D04C3")
}
; Replaces a script function with machine code.
Eval_mcode(FuncName, Hex)
{
if !(pFunc := Eval_getFuncUDF(FuncName)) or !(pbin := DllCall("GlobalAlloc","uint",0,"uint",StrLen(Hex)//2))
return 0
Loop % StrLen(Hex)//2
NumPut("0x" . SubStr(Hex,2*A_Index-1,2), pbin-1, A_Index, "char")
NumPut(pbin,pFunc+4), NumPut(1,pFunc+49,0,"char")
return pbin
}
Eval_static(var) {
; MCODE: Makes the specified Var static.
; Example usage:
; Eval_static(array%i%)
; Note that Eval_static could be implemented in script using Eval_getVar and
; NumPut. For two reasons I chose to implement it as machine code:
; - The machine code of Eval_static is shorter than that of Eval_getVar.
; - Eval_static may be called in high frequency (i.e. for arrays),
; so it is particularly beneficial for it to not spam ListLines.
}
Eval_getVar(var) {
; MCODE: Returns a pointer to the specified Var object, or 0 for built-in vars.
; Example usage:
; NumGet(Eval_getVar(%name%)+13,0,"char") ; Var.mAttrib
; NumGet(Eval_getVar(%name%)+13,0,"char") & 1 ; is ClipboardAll blob
; NumGet(Eval_getVar(%name%)+13,0,"char") & 4 ; is static
; NumGet(Eval_getVar(%name%)+12,0,"char") ; Var.mHowAllocated (zero if never init'd)
}
; Note that since the purpose of Eval_getVar() is to retrieve a Var pointer from
; the expression token which AutoHotkey passes it as a "built-in" function,
; - DllCall can not be used to call the machine code, and
; - we can't simply initialize the machine code from within Eval_getVar since
; it'd then be accessing the parameter 'var', not the variable which was
; passed. Making var ByRef would require Eval_mcode to use Eval_findFunc,
; which is less efficient (and spams ListLines more) than Eval_getFuncUDF.
; Eval_getVar benchmarks at 0.000015s in pure script mode, or 0.000002s in
; "built-in" mode. However, script mode entirely fills ListLines, whereas
; the built-in mode does not log ANY lines.
/* Script version:
Eval_getVar(ByRef var) {
Static pThisFunc
if !pThisFunc && !(pThisFunc := Eval_findFunc(A_ThisFunc))
return ; silently fail
return NumGet(NumGet(NumGet(pThisFunc+8)+0)+4) ; pGetVar->mParam[0].var->mAliasFor
}
; NOTE: In the following scenario, the script version of Eval_getVar gets
; a pointer to 'z', while the built-in version gets 'x' itself.
;
; f(z)
; f(ByRef x) {
; y := Eval_getVar(x)
; }
*/
Eval_getGlobalVar(Eval_gGV_sVarName)
{
global
return Eval_getVar(%Eval_gGV_sVarName%)
}
Eval_getBuiltInVar(sVarName)
{
static pDerefs, DerefCount
if !pDerefs ;= label->mJumpToLine->mArg[0]->deref
{
pDerefs := NumGet(NumGet(NumGet(Eval_findLabel("Eval_gBIV_marker"),4),4),8)
Loop
if ! NumGet(pDerefs+(A_Index-1)*12) {
DerefCount := A_Index-1
break
}
}
low := 0
high := DerefCount - 1
Loop {
if (low > high)
break
mid := (low+high)//2
i := DllCall("shlwapi\StrCmpNIA","uint",NumGet(pDerefs+mid*12),"str",sVarName,"int",NumGet(pDerefs+mid*12,10,"ushort"))
if i > 0
high := mid - 1
else if i < 0
low := mid + 1
else
return NumGet(pDerefs+mid*12,4)
}
return 0
Eval_gBIV_marker:
return % 0,
( Join`s
A_AhkPath A_AhkVersion A_AppData A_AppDataCommon A_AutoTrim A_BatchLines
A_CaretX A_CaretY A_ComputerName A_ControlDelay A_Cursor A_DD A_DDD A_DDDD
A_DefaultMouseSpeed A_Desktop A_DesktopCommon A_DetectHiddenText
A_DetectHiddenWindows A_EndChar A_EventInfo A_ExitReason A_FormatFloat
A_FormatInteger A_Gui A_GuiControl A_GuiControlEvent A_GuiEvent A_GuiHeight
A_GuiWidth A_GuiX A_GuiY A_Hour A_IconFile A_IconHidden A_IconNumber A_IconTip
A_Index A_IPAddress1 A_IPAddress2 A_IPAddress3 A_IPAddress4 A_IsAdmin
A_IsSuspended A_KeyDelay A_Language A_LastError A_LineFile A_LineNumber
A_LoopField A_LoopFileAttrib A_LoopFileDir A_LoopFileExt A_LoopFileFullPath
A_LoopFileLongPath A_LoopFileName A_LoopFileShortName A_LoopFileShortPath
A_LoopFileSize A_LoopFileSizeKb A_LoopFileSizeMb A_LoopFileTimeAccessed
A_LoopFileTimeCreated A_LoopFileTimeModified A_LoopReadLine A_MDay A_Min A_MM
A_MMM A_MMMM A_Mon A_MouseDelay A_MSec A_MyDocuments A_Now A_NowUtc
A_NumBatchLines A_OSType A_OSVersion A_PriorHotkey A_ProgramFiles A_Programs
A_ProgramsCommon A_ScreenHeight A_ScreenWidth A_ScriptDir A_ScriptFullPath
A_ScriptName A_Sec A_Space A_StartMenu A_StartMenuCommon A_Startup
A_StartupCommon A_StringCaseSense A_Tab A_Temp A_ThisFunc A_ThisHotkey
A_ThisLabel A_ThisMenu A_ThisMenuItem A_ThisMenuItemPos A_TickCount A_TimeIdle
A_TimeIdlePhysical A_TimeSincePriorHotkey A_TimeSinceThisHotkey
A_TitleMatchMode A_TitleMatchModeSpeed A_UserName A_WDay A_WinDelay A_WinDir
A_WorkingDir A_YDay A_Year A_YWeek A_YYYY Clipboard ClipboardAll ComSpec false
ProgramFiles true
)
}
; Executes a double-deref in the context of the specified function.
; Use Eval_findFunc() to get a pointer to a Func for use with pScopeFunc.
Eval_getVarInContext(sVarName, pScopeFunc=0)
{
static pThisFunc
if pVar:=Eval_getBuiltInVar(sVarName)
return pVar
if !pScopeFunc
return Eval_getGlobalVar(sVarName)
if !pThisFunc && !(pThisFunc := Eval_getFuncUDF(A_ThisFunc))
return
; Copy assume-local/global mode. Since it only affects double-derefs,
; it doesn't need to be restored to its previous value later.
NumPut(NumGet(pScopeFunc+48,0,"char"),pThisFunc+48,0,"char")
; Back up this function's properties,
VarSetCapacity(ThisFuncProps, 20)
, DllCall("RtlMoveMemory","uint",&ThisFuncProps,"uint",pThisFunc+20,"uint",20)
; then overwrite them with the other function's properties:
; mVar, mLazyVar, mVarCount, mVarCountMax, mLazyVarCount
, DllCall("RtlMoveMemory","uint",pThisFunc+20,"uint",pScopeFunc+20,"uint",20)
; WARNING:
; If the thread is interrupted at this point and Eval_getVarInContext is called
; again, the wrong set of local variables will be backed up and restored, so
; the second instance of Eval_getVarInContext will overwrite the local vars
; of the first instance. Merging the lines (with ", ") prevents AutoHotkey
; from checking for messages, thus reducing the risk of interruption.
; (This would not work fully if Eval_getVar were implemented in script.)
; Now resolve %sVarName% in the scope of the other func!
, pVar := Eval_getVar(%sVarName%)
; Update pScopeFunc's properties.
, DllCall("RtlMoveMemory","uint",pScopeFunc+20,"uint",pThisFunc+20,"uint",20)
; Restore this function's properties.
, DllCall("RtlMoveMemory","uint",pThisFunc+20,"uint",&ThisFuncProps,"uint",20)
return pVar
}
; Lists all accessible functions.
Eval_listFuncs(UserFunctionsOnly=False)
{
; if UserFunctionsOnly, also exclude functions in THIS file.
this_file_index := NumGet(NumGet(Eval_getFuncUDF(A_ThisFunc)+4),2,"ushort")
if !(pFunc := Eval_getFirstFunc())
return
Loop {
if !UserFunctionsOnly || !NumGet(pFunc+49,0,"char") && NumGet(NumGet(pFunc+4),2,"ushort") != this_file_index
list .= Eval_str(NumGet(pFunc+0)) "`n"
if !(pFunc := NumGet(pFunc+44)) ; pFunc->mNextFunc
break
}
return SubStr(list,1,-1)
}
; Func *findFunc( FuncName, BuiltIn )
; FuncName name of the function
; BuiltIn true to search only for built-in functions
; false to search only for user-defined functions
; empty to search for any type of function.
Eval_findFunc(FuncName, BuiltIn="")
{
; Do not change the order here (see the "combination" method in the comment below.)
if ((pFunc:=Eval_getFuncUDF(FuncName)) && !BuiltIn)
return pFunc
if !(pFunc:=Eval_getFirstFunc())
return 0
Loop { ; Note: ! is used here to ensure both values are true boolean 1 or 0.
if (BuiltIn = "" or (!NumGet(pFunc+49,0,"uchar") = !BuiltIn))
if (Eval_str(NumGet(pFunc+0)) = FuncName) ; pFunc->mName
return pFunc
if !(pFunc := NumGet(pFunc+44)) ; pFunc->mNextFunc
break
}
return 0
}
; Possible methods for finding a Func:
; - RegisterCallback & NumGet. This approach is likely the most efficient,
; and is also used as an entry point (by Eval_getFirstLine()).
; However, RegisterCallback fails for built-in functions or user-defined
; functions which have ByRef parameters.
; - Search for function "derefs" in each script line. This does not work for
; functions which are not referenced anywhere in the script.
; - Search through the linked list of functions. This is reliable as long as
; we have a pointer to the first function. This is usually the first function
; defined in the script or its explicit #includes, if any.
; - A combination of all of the above:
; + Though RegisterCallback does not work for built-in functions, calling
; RegisterCallback for a built-in function will cause it's Func object
; to be created if it doesn't already exist. Calling Eval_getFuncUDF() ensures
; that the built-in function we are looking for is in the linked list.
; + If Eval_getFuncUDF() succeeds AND we are not specifically looking for a
; built-in function, no more work is necessary.
; The first time Eval_getFuncUDF fails:
; + Because searching through derefs is likely to take longer than walking
; the linked list of functions, we search through derefs only to find a
; pointer to the first function in the list. (See Eval_getFirstFunc()).
; For all calls of Eval_findFunc() where Eval_getFuncUDF() fails:
; + We walk the linked list to find the function.
; Func *getFirstFunc()
; Gets the first Func in the linked list of functions, excluding unreferenced
; functions which are not preceded by any referenced functions.
Eval_getFirstFunc()
{
static pFirstFunc
if !pFirstFunc {
if !(pLine := Eval_getFirstLine())
return 0
Loop {
Loop % NumGet(pLine+1,0,"uchar") { ; pLine->mArgc
pArg := NumGet(pLine+4) + (A_Index-1)*12 ; pLine->mArg[A_Index-1]
if (NumGet(pArg+0,0,"uchar") != 0) ; pArg->type != ARG_TYPE_NORMAL
continue ; arg has no derefs (only a Var*)
Loop {
pDeref := NumGet(pArg+8) + (A_Index-1)*12 ; pArg->deref[A_Index-1]
if (!NumGet(pDeref+0)) ; pDeref->marker (NULL terminates list)
break
if (NumGet(pDeref+8,0,"uchar")) ; pDeref->is_function
{
; The first function is either the first defined function,
; or if no explicitly #included UDFs exist, the first
; built-in function referenced in code.
pFunc := NumGet(pDeref+4)
if (NumGet(pFunc+49,0,"uchar")) { ; pFunc->mIsBuiltIn
if !pFirstBIF
pFirstBIF := pFunc
} else { ; UDF
pFuncLine := NumGet(pFunc+4)
FuncLine := NumGet(pFuncLine+8)
FuncFile := NumGet(pFuncLine+2,0,"ushort")
if !pFirstFunc or (FuncFile < FirstFuncFile || (FuncFile = FirstFuncFile && FuncLine < FirstFuncLine))
pFirstFunc:=pFunc, FirstFuncLine:=FuncLine, FirstFuncFile:=FuncFile
}
}
}
}
if !(pLine:=NumGet(pLine+20)) ; pLine->mNextLine
break
}
if pFirstBIF
{ ; Usually the first UDF will be before the first BIF, but not if
; only auto-included/stdlib UDFs exist *AND* the first BIF is
; referenced in code before the first UDF.
if pFirstFunc
{ ; Look for the BIF using the UDF as a starting point.
pFunc := pFirstFunc
Loop {
if !(pFunc := NumGet(pFunc+44)) ; pFunc->mNextFunc
break
; If the BIF is found, the UDF must precede it in the list.
if (pFunc = pFirstBIF)
return pFirstFunc
}
; If we got here, the BIF was not found, so is probably the first Func.
}
pFirstFunc := pFirstBIF
return pFirstFunc
}
}
return pFirstFunc
}
Eval_listLabels(UserLabelsOnly=False)
{
this_file_index := NumGet(NumGet(Eval_getFuncUDF(A_ThisFunc)+4),2,"ushort")
if pLabel := Eval_getFirstLabel()
Loop {
if !UserLabelsOnly || NumGet(NumGet(pLabel+4),2,"ushort") != this_file_index
list .= Eval_str(NumGet(pLabel+0)) "`n"
if ! pLabel := NumGet(pLabel+12)
break
}
return SubStr(list,1,-1)
}
Eval_findLabel(sLabel)
{
if pLabel := Eval_getFirstLabel()
Loop {
if Eval_str(NumGet(pLabel+0)) = sLabel
return pLabel
if ! pLabel := NumGet(pLabel+12)
return 0
}
}
Eval_getFirstLabel()
{
static pFirstLabel
if !pFirstLabel {
if !(pLine := NumGet(Eval_getFuncUDF(A_ThisFunc)+4))
return 0
Loop {
act := NumGet(pLine+0,0,"char")
if (act = 96 || act = 95) ; ACT_GOSUB || ACT_GOTO
break
if !(pLine:=NumGet(pLine+20)) ; pLine->mNextLine
return 0
}
pFirstLabel := NumGet(pLine+24)
Loop {
if ! pPrevLabel:=NumGet(pFirstLabel+8)
break
pFirstLabel := pPrevLabel
}
}
return pFirstLabel
; Since Labels are in a doubly-linked list, we can find the first label
; by getting the Label associated with the goto line below.
Eval_getFirstLabel_HookLabel:
goto Eval_getFirstLabel_HookLabel
}
; Line *getFirstLine()
Eval_getFirstLine()
{
static pFirstLine
if (pFirstLine = "") {
if pThisFunc := Eval_getFuncUDF(A_ThisFunc) {
if pFirstLine := NumGet(pThisFunc+4) ; mJumpToLine
Loop {
if !(pLine:=NumGet(pFirstLine+16)) ; mPrevLine
break
pFirstLine := pLine
}
}
}
return pFirstLine
}
; Func *getFuncUDF( FuncName )
; Gets a pointer to a named user-defined function. Because RegisterCallback
; doesn't support functions with ByRef parameters, neither does this function.
Eval_getFuncUDF(FuncName) {
if pCb := RegisterCallback(FuncName) {
func := NumGet(pCb+28)
DllCall("GlobalFree","uint",pCb)
}
return func
}
Eval_str(addr,len=-1) {
if len<0
return DllCall("MulDiv","uint",addr,"int",1,"int",1,"str")
VarSetCapacity(str,len), DllCall("lstrcpyn","str",str,"uint",addr,"int",len+1)
return str
}
Eval_(expr, pScopeFunc=0)
{
global Eval_init_done
static pFunc, pThisFunc
if !Eval_init_done
Eval_init()
if !pFunc
pFunc:=Eval_getFuncUDF("Eval_sub"), pThisFunc:=Eval_getFuncUDF(A_ThisFunc)
if ! pArg:=Eval_MakeExpressionArg(expr, pScopeFunc)
return
nInst := NumGet(pThisFunc+40)
; Using Eval_static ensures the Line is never deleted. Attempting to view a
; a deleted line with ListLines would crash the script.
; Using a static *array* allows recursion of Eval_expr.
; Using nInst (recursion depth) as an array index preserves memory by
; only allocating up to the maximum number of resursive instances, while
; also ensuring that no other instance is using the same block of memory.
VarSetCapacity(Line%nInst%,32,0), Eval_static(Line%nInst%), pLine:=&Line%nInst%
NumPut(pArg,NumPut(1,NumPut(102,pLine+0,0,"char"),0,"char"),2), NumPut(pLine,NumPut(pLine,pLine+16))
NumPut(pLine,pFunc+4)
, ret := Eval_sub()
, NumPut(0,pLine+1,0,"char"), DllCall("GlobalFree","uint",pArg)
return ret
}
Eval_sub() {
global
; Contents replaced at run-time by Eval_expr.
}
eval_test(this){
msgbox % this
}
Eval_MakeExpressionArg(expr, pScopeFunc=0)
{
static OPERAND_TERMINATORS = "<>=/|^,:"" `t*&~!()+-"
; VALIDATION AND SINGLE-QUOTES
i = 0
open_parens = 0
Loop {
if (c:=SubStr(expr,++i,1)) = ""
break
; Ensure open-" has matching close-"
if c = "
{
if ! i:=InStr(expr,"""",1,i+1)
return 0, ErrorLevel:="Missing close-quote ("")"
}
; Allow ' in place of ", and ensure it has a matching close-'
else if c = '
{
if ! j:=InStr(expr,"'",1,i+1)
return 0, ErrorLevel:="Missing close-quote (')"
literal := SubStr(expr,i+1,j-i-1)
StringReplace, literal, literal, ", "", UseErrorLevel
expr := SubStr(expr,1,i-1) . """" . literal . """" . SubStr(expr,j+1)
i := j + ErrorLevel
}
; Ensure parentheses are balanced.
else if c = (
open_parens += 1
else if c = )
open_parens -= 1
}
if open_parens > 0
return 0, ErrorLevel:="Missing "")"""
if open_parens < 0
return 0, ErrorLevel:="Missing ""("""
; PARSE DEREFS
num_derefs = 0
pos = 1
Loop {
; includes \. to avoid interpreting the '0e1' in '1.0e1' as a var.
if ! pos:=RegExMatch(expr, """.*?""|[\w#@$?\[\]\.%]+", word, pos)
break
marker := pos-1
pos += StrLen(word)
if SubStr(word,1,1)="""" ; skip quoted literal strings
continue
if InStr(word,".") or word+0!="" ; number or error
continue
if word = ?
continue
param_count = 0
if is_func:=SubStr(expr,pos,1)="("
{
if InStr(word, "%")
return 0, Errorlevel:="Dynamic function calls are not supported."
if ! var_or_func:=Eval_findFunc(word)
return 0, ErrorLevel:="Call to nonexistent function """ word """."
i := pos
open_parens = 1
Loop {
if (c:=SubStr(expr,++i,1))=""
break
if (param_count=0 && !(c=" "||c="`t"||c=")"))
param_count = 1
if c = ,
param_count += open_parens=1 ? 1 : 0
else if c = (
open_parens += 1
else if c = )
if --open_parens = 0
break
}
if (param_count < NumGet(var_or_func+16))
return 0, ErrorLevel:="Too few parameters passed to function """ word """."
if (param_count > NumGet(var_or_func+12))
return 0, ErrorLevel:="Too many parameters passed to function """ word """."
}
else
{
if i:=InStr(full_word:=word, "%") {
original_marker := marker
Loop {
if !(j:=InStr(full_word, "%", 1, i+1)) or j-i<2
return 0, ErrorLevel:="Invalid deref """ full_word """."
word_len := StrLen( word := SubStr(full_word, i, j-i+1) )
marker := original_marker + i-1
if var_or_func:=Eval_getVarInContext(SubStr(word,2,-1), pScopeFunc)
gosub Eval_MakeExpressionArg_AddDeref
if ! i:=InStr(full_word, "%", 1, j+1)
break
}
continue
}
var_or_func := Eval_getVarInContext(word, pScopeFunc)
}
if var_or_func
{
word_len := StrLen(word)
gosub Eval_MakeExpressionArg_AddDeref
}
}
; Allocate: sizeof(ArgStruct) + StrLen(expr)+1 + (num_derefs+1)*sizeof(DerefType)
pArg := DllCall("GlobalAlloc","uint",0x40,"uint",25+StrLen(expr)+num_derefs*12)
DllCall("lstrcpy","uint",pArg+12,"str",expr)
NumPut(pDerefs:=pArg+13+StrLen(expr),NumPut(pArg+12,NumPut(StrLen(expr),NumPut(1,NumPut(0,pArg+0,0,"char"),0,"char"),0,"short")))
Loop, %num_derefs% {
DllCall("RtlMoveMemory","uint",pDeref:=pDerefs+A_Index*12-12,"uint",&deref%A_Index%,"uint",12)
NumPut(pArg+12+NumGet(pDeref+0),pDeref+0) ; deref.marker += base_address_of_text
}
return pArg, ErrorLevel=0
Eval_MakeExpressionArg_AddDeref:
num_derefs += 1
VarSetCapacity(deref%num_derefs%,12)
NumPut(word_len,NumPut(param_count,NumPut(is_func,NumPut(var_or_func,NumPut(marker,deref%num_derefs%)),0,"char"),0,"char"),0,"short")
; Note: marker must be adjusted later: pos += base_address_of_text
return
}
; WHAT IS NORMALLY DONE AT LOAD-TIME:
; - Replacing 'AND' and 'OR' with '&&' and '||'.
; - Ensuring parentheses are balanced. -- DONE
; - Ensuring literal quoted strings have a closing quote mark. -- DONE
; - Validating parameter count in function calls. -- DONE
; - Validating ByRef parameters in function calls.
; This is also done at run-time, so need not be done by us.
@lexikos,
damned nice stuff!