Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

LowLevel & dynamic code


  • Please log in to reply
173 replies to this topic
Joy2DWorld
  • Members
  • 562 posts
  • Last active: Jun 30 2014 07:48 PM
  • Joined: 04 Dec 2006
I must be missing something. Skimmed over code, looks like ResultType Line::ExpressionToPostfix is general area of tokenizing start. Can't this just tokenize into var memory & then eval just like current eval does ?

clearly you're lot more intimate with the code, and maybe i'm just not understanding something basic.

Lexikos
  • Administrators
  • 9451 posts
  • Last active:
  • Joined: 17 Oct 2006
ExpressionToPostfix relies on AddLine having already resolved variable and function references. This is mostly for historic reasons; i.e. from when only this was done at load-time and the rest done at run-time. I intend to change this when I work on proper dynamic script evaluation.

See the second paragraph in my previous post.

Looks like I omitted a key piece of information: AutoHotkey allocates "permanent" memory for the expression tokens in order to avoid the overhead of dynamic memory. This is the "important memory optimisation" I mentioned earlier. If a script were to evaluate dynamic script on a loop or timer, memory usage would gradually increase.

If I'd found a simple method (without the aforementioned caveat), I'd have implemented it already.

tinku99
  • Members
  • 560 posts
  • Last active: Nov 18 2013 02:43 AM
  • Joined: 03 Aug 2007
was that just to demo the ability to create expressions with lowlevel by hand?
Otherwise, does call() add anything over this?
calls(function, var1 = 0, var2 = 0, var3 = 0, var4 = 0, var5 = 0 
, var6 = 0, var7 = 0, var8 = 0, var9 = 0) 
  { 
return %function%(var1, var2, var3, var4, var5, var6, var7, var8, var9)
}


Lexikos
  • Administrators
  • 9451 posts
  • Last active:
  • Joined: 17 Oct 2006

Otherwise, does call() add anything over this?

Before v1.0.48, your code would fail if the function had less than 9 parameters.

tinku99
  • Members
  • 560 posts
  • Last active: Nov 18 2013 02:43 AM
  • Joined: 03 Aug 2007

(If Line::ExpressionToPostfix() were exposed to script it would be possible, but permanent memory would be allocated for each expression evaluated.)


I may be able to export this function in the dll. If you reenable __ParseExpressionArg() in low level, maybe can we just pass the arg result from __ParseExpressionArg to ExpressionToPostfix() ?

Also, I was able to mix your anonymous function and Call() to make a function wrapper for 1.0.48:

; __curry
/*
modified anonymous function creation by tinku99
original function by Lexikos
Requires AutoHotkey pre-v1.0.48 beta
*/

x = 3
y = 5
message = "hello"
__curry("add", "curryAdd", x, y)
__curry("alert", "curryAlert", message)
return

alert(A)
{
msgbox % A
}

add(a, b)
{
msgbox % a + b
}

F1::
func = curryAlert
%func%()
return

F2::
func2 = curryAdd
%func2%() 
return


F3::Reload


__curry(FunctionName, newFunc, ByRef p1="__deFault__", ByRef p2="__deFault__", ByRef p3="__deFault__", ByRef p4="__deFault__", ByRef p5="__deFault__", ByRef p6="__deFault__", ByRef p7="__deFault__", ByRef p8="__deFault__", ByRef p9="__deFault__", ByRef p10="__deFault__")
{
Static  
pn:="1,2,3,4,5,6,7,8,9,10", SYM_INVALID:=56, SYM_VAR:=3, SYM_FUNC:=55, SYM_OPERAND:=4, VAR_ALIAS:=0

    if !count
        LowLevel_init()
    if !(func := __findFunc(FunctionName))
        return
    if NumGet(func+16) > 10 ; Give up, requires too many parameters.
        return


if !count
count = 1

cg := code_gen()
code_func(cg, newFunc)  ; "MyFunc"
;; Define the parameters.


loop, 8
{
Param%A_Index%Var := code_func_param(cg, chr(97 + A_Index), 0, 1, "__deFault__") ; "ABC..."
}


code_func_end(cg)
;; Terminate the parameter list.

;; Define the body of the function.
code_line(cg, ACT_BLOCK_BEGIN:=107)
 ;; code_line(cg, ACT_MSGBOX:=34)
eline := code_line(cg, ACT_EXPRESSION ) 
code_arg(cg, 0, 1)
code_line(cg, ACT_RETURN:=102)
code_line(cg, ACT_BLOCK_END:=108)

arg := NumGet(eline+0, 4)

   
    ; Get pointer to alias var for use in the loop.


    this_param_var := __getVar(this_param)

    ; postfix%count% must fit one ExprTokenType for each param, followed by a SYM_FUNC token
    ; representing a function call, a SYM_INVALID token terminating the expression and
    ; a DerefType holding a pointer to the function to call.
    VarSetCapacity(postfix%count%, 204, 0) ; 16*(10+2) + 12*1
    this_postfix := &postfix%count%
   
    pc := 0
    Loop, Parse, pn, `,
    {
        ; Ignore any parameters beyond the function's capacity.
        if (pc >= NumGet(func+12))
            break
       
        if (p%A_LoopField% == "__deFault__")
        {
            ; If this parameter is required, just pass empty string.
            if (pc < NumGet(func+16))
                p%A_LoopField% =
            else
                break
        }
       
        var :=  __getVar(p%A_LoopField%) ; Param%A_LoopField%Var 
        ; Resolve ByRef/alias.
        if (NumGet(var+23,0,"char") = VAR_ALIAS)
            var := NumGet(var+12)
       
        ; Put param var into postfix%count% array.
        NumPut(var, this_postfix+0), NumPut(SYM_VAR, this_postfix+8)
       
        ; Move to next token in postfix%count% array, increment param count.
        this_postfix += 16
        pc++
    }
   
    ; Set up the DerefType at the end of the postfix%count% array.
    func_deref := this_postfix+32
    NumPut(func, func_deref+4)
    NumPut(pc, func_deref+9, 0, "char")
   
    ; Set up SYM_FUNC token.
    NumPut(func_deref, this_postfix+0)
    NumPut(SYM_FUNC, this_postfix+8)
    this_postfix += 16
   
    ; Terminate postfix%count% array.
    NumPut(SYM_INVALID, this_postfix+8)
   
    ; Set postfix%count% array of the first arg of 'return' below.
    NumPut(&postfix%count%, arg+12)
    hcode := code_finalize(cg, 1)
    code_gen_delete(cg)
    count++
    return
}
  
#Include Lowlevel.ahk  ; v1.0.48
#Include code.ahk  ; v1.0.48


however, when i tried to adapt this to do function hooks, the script crashes:
adviseUDF(advice, type, advisee)  ; type = before or after
{
static
ACT_EXPRESSION:=3, _sizeof_FuncParam:=16, _Var_Name:=24
_JumpToLine:=4, _Param:=8, _ParamCount:=12, _MinParams:=16, _IsBuiltIn:=49
pn:="1,2,3,4,5,6,7,8,9,10", SYM_INVALID:=56, SYM_VAR:=3, SYM_FUNC:=55, SYM_OPERAND:=4, VAR_ALIAS:=0

count = 1

    LowLevel_init()
    if !(func := __findFunc(advice))
        return
    if NumGet(func+16) > 10 ; Give up, requires too many parameters.
        return

    ; Resolve functions and validate.
    if !(advice_func := __findFunc(advice))
        || !(advisee_func := __findFunc(advisee))
        || NumGet(advisee_func+_IsBuiltIn,0,"char")
        return false
 param_count := NumGet(advisee_func+_ParamCount)
 

    ; Begin generating code.
    cg := code_gen()

    ; Begin a standalone expression. Each standalone expression has only one arg.
eline := code_line(cg, ACT_EXPRESSION ) 
code_arg(cg, 0, 1)
 ; line := code_line(cg, ACT_GOSUB := 96)  
arg := NumGet(eline+0, 4)

    ; postfix%count% must fit one ExprTokenType for each param, followed by a SYM_FUNC token
    ; representing a function call, a SYM_INVALID token terminating the expression and
    ; a DerefType holding a pointer to the function to call.
    VarSetCapacity(postfix%count%, 204, 0) ; 16*(10+2) + 12*1
    this_postfix := &postfix%count%
   param := NumGet(func+_Param)
    pc := 0
    Loop, 2
    {
        ; Ignore any parameters beyond the function's capacity.
        if (pc >= NumGet(func+12))
            break
       
        var := __getvar(NumGet(param+0))
        ; Resolve ByRef/alias.
        if (NumGet(var+23,0,"char") = VAR_ALIAS)
            var := NumGet(var+12)
       
        ; Put param var into postfix%count% array.
        NumPut(var, this_postfix+0), NumPut(SYM_VAR, this_postfix+8)

        ; Move to next token in postfix%count% array, increment param count.
        this_postfix += 16
        pc++
        param += _sizeof_FuncParam

    }
   
    ; Set up the DerefType at the end of the postfix%count% array.
    func_deref := this_postfix+32
    NumPut(func, func_deref+4)
    NumPut(pc, func_deref+9, 0, "char")
   
    ; Set up SYM_FUNC token.
    NumPut(func_deref, this_postfix+0)
    NumPut(SYM_FUNC, this_postfix+8)
    this_postfix += 16
   
    ; Terminate postfix%count% array.
    NumPut(SYM_INVALID, this_postfix+8)
   
    ; Set postfix%count% array of the first arg of 'return' below.
    NumPut(&postfix%count%, arg+12)

if (type == "after")
{
  line1 := Numget(advisee_func+_JumpToLine, 0, "UInt")
  loop
  {
  lineAct := NumGet(line1+0, 0, "UChar")
    if (lineAct != 102)
      line1 := Numget(line1+0, 20, "UInt")
    else 
      break
  }
  
  {
    if (ch := code_finalize(cg))
    {
      ; Insert the code before the first return statement
      code_insert_before(ch, line1)
      code_delete_handle(ch)
    }
    code_gen_delete(cg)
    return !!ch
  }
}
else
{
    if (ch := code_finalize(cg))
    {
        ; Insert the code before the jump-to line of the function.
        code_insert_before(ch, NumGet(advisee_func+_JumpToLine))
        ; Re-target the function at the new line.
        NumPut(eline, advisee_func+_JumpToLine)
        code_delete_handle(ch)
    }
    code_gen_delete(cg)
    return !!ch
  }
  
}
test advice
myFunc("Apple","banana")
adviseUDF("SwapParameters","before","myFunc")
myFunc("Apple","banana")
return

myFunc(A,B) {
msgbox % A . " meet " . B
return
}

SwapParameters(ByRef A, ByRef B) {
    C := A
    A := RegExReplace(B,"^\w","$U0")
    B := RegExReplace(C,"^\w","$l0")
return
}

advice(ByRef A, ByRef B)
{
msgbox, swapped
return
}



Lexikos
  • Administrators
  • 9451 posts
  • Last active:
  • Joined: 17 Oct 2006

I may be able to export this function in the dll. If you reenable __ParseExpressionArg() in low level, maybe can we just pass the arg result from __ParseExpressionArg to ExpressionToPostfix() ?

That may work. You may find the old implementation of __ParseExpressionArg in LowLevel_104706.zip.

Also, I was able to mix your anonymous function and Call() to 1.0.48:

Well done!

eline := code_line(cg, ACT_EXPRESSION )
code_arg(cg, 0, 1)
[color=green]code_line_end(cg)[/color]  ; <-- add this
[color=red]arg := NumGet(eline+0, 4)[/color]
Args are not attached to the line and removed from the codegen's internal buffer until the line is "finished" via code_line_end. This is done automatically, but only if and when you call code_line (to begin the next line) or code_finalize. At the time your code retrieves the Arg array, it is NULL.
var :=[color=red] __getvar([/color]NumGet(param+0)[color=red])[/color]
__getvar returns a pointer to a Var structure, given a variable reference like var or %varContainingNameOfVar%. If it is passed anything else, it returns 0. Since what NumGet returns is already a pointer to a Var structure, you simply need to remove __getvar().

The next problem (which was causing the crash) was difficult to find until I ran the script with the Visual C++ debugger. Normally each arg is assigned text, and this text is later parsed to form the postfix array. When ListLines is called and the dynamic line must be converted to text, AutoHotkey sees that the text is empty and assumes the arg has an associated input/output variable. Since it does not, this causes an access violation. The simplest solution is to add a static variable dynamic_text:="[dynamic]", and the following:
NumPut(&dynamic_text, arg+4)
The final problem was difficult to find, but an easy fix:
param := NumGet([color=red]func[/color]+_Param)
Your copy-pasting has resulted in two variables which point to the same function: func and advice_func. Since param is used to retrieve variable references to pass to the advice function (also referred to as 'func'), it essentially passes the advice function's parameters back to itself. Change func to advisee_func.


There are additional changes I would recommend, to clean up the code, improve performance and re-enable essential features. First, remove the following lines:
if !(func := __findFunc(advice))
    return
if NumGet(func+16) > 10 ; Give up, requires too many parameters.
    return
Next, replace all references to the variable func with advice_func, which contained the same value. Note that Call() imposed a limit of 10 parameters only because it could not accept more (without modification).

Since we've removed the limit of 10 parameters, the buffer size we set may be inadequate:
; VarSetCapacity(postfix%count%, [color=red]204[/color], 0)
VarSetCapacity(postfix%count%, [color=green]16*(param_count+2)+12[/color], 0)
In order to support functions with <> 2 parameters, change Loop, 2 to Loop, %param_count%.

To support multiple "advice connections", we need to initialize count only once and increment it each time.

Using assume-static means two things:
[*:19tx1tsw]Several static variables which only need to be initialized once are initialized each time.
[*:19tx1tsw]All local variables are static; this may cause conflicts in the rare event that the thread is interrupted and adviseUDF() is called while a previous instance is still running.Rather than explicitly declaring each non-static local variable, it seems easier to use explicit static declarations and __static for the dynamic static variables.

Rather than the simple dynamic_text method described above, we can store more useful information in the arg's text - for instance, [advise myadvice before func].

Finally, if we are using AutoHotkey v1.0.48.01 or later, we can use ListLines On/Off to make debugging much easier. AutoHotkey_L, which has not yet caught up, will ignore the On/Off part.
adviseUDF(advice, type, advisee)  ; type = before or after
{
    static ACT_EXPRESSION:=3, _sizeof_FuncParam:=16, _Var_Name:=24
        , _JumpToLine:=4, _Param:=8, _ParamCount:=12, _MinParams:=16, _IsBuiltIn:=49
        , pn:="1,2,3,4,5,6,7,8,9,10", SYM_INVALID:=56, SYM_VAR:=3, SYM_FUNC:=55, SYM_OPERAND:=4, VAR_ALIAS:=0
        , count = 0

    ErrorLevel := ErrorLevel  ; As of v1.0.48.02, this line is omitted from ListLines. Its
    ListLines Off             ; presence allows the line which called adviseUDF() to be seen.    
    LowLevel_init()

    ; Resolve functions and validate.
    If !(advice_func := __findFunc(advice))
        || !(advisee_func := __findFunc(advisee))
        || NumGet(advisee_func+_IsBuiltIn,0,"char")
    {
        ListLines On
        return false
    }
    param_count := NumGet(advisee_func+_ParamCount)

    ; Begin generating code.
    cg := code_gen()

    ; Begin a standalone expression. Each standalone expression has only one arg.
    eline := code_line(cg, ACT_EXPRESSION )
    code_arg(cg, 0, 1)
    code_line_end(cg)
    arg := NumGet(eline+0, 4)

    ; postfix%count% must fit one ExprTokenType for each param, followed by a SYM_FUNC token
    ; representing a function call, a SYM_INVALID token terminating the expression and
    ; a DerefType holding a pointer to the function to call.
    count += 1
    __static(postfix%count%), __static(text%count%:="[advise " advice " " type " " advisee "]")
    VarSetCapacity(postfix%count%, 16*(param_count+2)+12, 0) ; 16*(MAX_PARAMS+2) + 12*1
    this_postfix := &postfix%count%
    param := NumGet(advisee_func+_Param)
    pc := 0
    Loop, % param_count
    {
        ; Ignore any parameters beyond the function's capacity.
        If (pc >= NumGet(advice_func+12))
            break

        var := NumGet(param+0)
        ; Resolve ByRef/alias.
        If (NumGet(var+23,0,"char") = VAR_ALIAS)
            var := NumGet(var+12)

        ; Put param var into postfix%count% array.
        NumPut(var, this_postfix+0), NumPut(SYM_VAR, this_postfix+8)

        ; Move to next token in postfix%count% array, increment param count.
        this_postfix += 16
        pc++
        param += _sizeof_FuncParam

    }

    ; Set up the DerefType at the end of the postfix%count% array.
    func_deref := this_postfix+32
    NumPut(advice_func, func_deref+4)
    NumPut(pc, func_deref+9, 0, "char")

    ; Set up SYM_FUNC token.
    NumPut(func_deref, this_postfix+0)
    NumPut(SYM_FUNC, this_postfix+8)
    this_postfix += 16

    ; Terminate postfix%count% array.
    NumPut(SYM_INVALID, this_postfix+8)

    ; Set postfix%count% array of the first arg of 'return' below.
    NumPut(&postfix%count%, arg+12)
    NumPut(&text%count%, arg+4)

    If (type == "after")
    {
        line1 := Numget(advisee_func+_JumpToLine, 0, "UInt")
        Loop
        {
            lineAct := NumGet(line1+0, 0, "UChar")
            If (lineAct != 102)
                line1 := NumGet(line1+0, 20, "UInt")
            Else
                break
        }

        If (ch := code_finalize(cg))
        {
            ; Insert the code before the first return statement
            code_insert_before(ch, line1)
            code_delete_handle(ch)
        }
    }
    Else
    {
        If (ch := code_finalize(cg))
        {
            ; Insert the code before the jump-to line of the function.
            code_insert_before(ch, NumGet(advisee_func+_JumpToLine))
            ; Re-target the function at the new line.
            NumPut(eline, advisee_func+_JumpToLine)
            code_delete_handle(ch)
        }
    }
    code_gen_delete(cg)
    ListLines On
    return !!ch
}


myFunc("Apple","banana")
adviseUDF("SwapParameters","before","myFunc")
myFunc("Apple","banana")
adviseUDF("EXCLAIM","before","myFunc")
adviseUDF("LogParams","before","myFunc")
myFunc("Apple","banana")
adviseUDF("LogParams","after","threeparams")
threeparams("alpha","bravo","charlie")
ListLines
Pause

threeparams(a,b,c) {
    a=%a%
    return
}
myFunc(A,B) {
    MsgBox 0, myFunc, %A%, meet %B%.
    return
}
Exclaim(ByRef A, ByRef B) {
    StringUpper, A, A
    StringUpper, B, B
}
SwapParameters(ByRef A, ByRef B) {
    C := A
    A := RegExReplace(B,"^\w","$U0")
    B := RegExReplace(C,"^\w","$l0")
}
LogParams(a="", b="", c="") {
    MsgBox 0, LogParams, %a%,%b%,%c%
}


tinku99
  • Members
  • 560 posts
  • Last active: Nov 18 2013 02:43 AM
  • Joined: 03 Aug 2007
Lexikos, Thanks for fixing my broken adviseUDF() function. And also for the explanations on what was wrong.
Sorry, the code was a little messy... :oops:
The __curry() function was cleaner, but as things weren't working, i broke perfectly good parts of adviseUDF().

:p
anyways, I have good news also.
I was able to wrap more than just ExpressionToPostfix()

<!-- m -->http://www.autohotke... ... 371#264371<!-- m -->

Lexikos
  • Administrators
  • 9451 posts
  • Last active:
  • Joined: 17 Oct 2006
tinku99 recently pointed out that LowLevel is not currently compatible with AutoHotkey_L. I have been aware of this for a while, but LowLevel has been very low on my list of priorities. I have been considering how to improve the LowLevel functions to improve compatibility and more importantly to reduce the amount of work required to maintain compatibility. I intend to build some or all functions into AutoHotkey_L, but also maintain a version of LowLevel.ahk to be used with mainstream AutoHotkey. Scripts should be able to use at least a subset of features with either LowLevel.ahk or AutoHotkey_L, without version checks and the like.

For improving and maintaining compatibility, the basic idea is to abstract as much as feasible. There are a number of forms this could take:
value := __get(structPtr, "StructType", "FieldName")
__set(structPtr, "StructType", "FieldName", value)

value := __StructType(structPtr, "FieldName")
__StructType(structPtr, "FieldName=", value)

value := __StructType_FieldName(structPtr) ; get
__StructType_FieldName(structPtr, value) ; set
value := NumGet(structPtr + _StructType_FieldName_Offset, 0, _StructType_FieldName_Type)
NumPut(value, structPtr  + _StructType_FieldName_Offset, 0, _StructType_FieldName_Type)

value := NumGet(structPtr + __offset("StructType.FieldName"), 0, __type("StructType.FieldName"))
NumPut(value, structPtr + __offset("StructType.FieldName"), 0, __type("StructType.FieldName")))
I'd prefer not to implement anything that encroaches on proper struct handling, but I think whichever way I go will become obsolete when proper struct handling is introduced (assuming it comes after built-in LowLevel features).


My discussion with tinku99 began with mention of LowLevel use in a rosettacode task/example. I'd prefer LowLevel not be exposed outside of the AutoHotkey community, mainly because of its inherent version-dependency and "ungainly form".

The task in question was Address Operations. The most obvious way to do this is __alias; although it is safer than literally getting or setting the address of the variable's contents, it does not strictly adhere to the task. Although I'd still prefer LowLevel not be used at rosettacode, I thought a more accurate example could be of some interest to the AutoHotkey community:
; Expected result: varA and varB show the same value and address only at the first MsgBox.

; AutoHotkey v1.0.48.03
_Var_Contents_Offset := 8
_Var_Contents_Type := "UInt"
_Var_Capacity_Offset := 16
_Var_Capacity_Type := "UInt"
_Var_HowAllocated_Offset := 20
_Var_HowAllocated_Type := "UChar"
_ALLOC_SIMPLE := 1

; Initialize low-level library.
LowLevel_init()

; Initialize variable.
VarSetCapacity(varA, 26)

; Get address of the Var structure which describes the variable.
varPtrA := __getVar(varA)
varPtrB := __getVar(varB)

; Get address of data (equivalent to &varA):
dataPtr := NumGet(varPtrA + _Var_Contents_Offset, 0, _Var_Contents_Type)
if (dataPtr != &varA) {
    MsgBox Internal error retrieving data address! Expected '%varA%', got '%dataPtr%'.
    ExitApp
}

; Check allocation method. It should never be ALLOC_NONE, which means no data.
; ALLOC_SIMPLE means persistent memory which cannot be freed until the process terminates.
; ALLOC_MALLOC means dynamic memory which may be freed at some point.
varA_HowAllocated := NumGet(varPtrA + _Var_HowAllocated_Offset, 0, _Var_HowAllocated_Type)
if (varA_HowAllocated != _ALLOC_SIMPLE)
    MsgBox Warning: varA allocation method '%varA_HowAllocated%', data may be freed unexpectedly.

; Set address of data:
NumPut(dataPtr, varPtrB + _Var_Contents_Offset, 0, _Var_Contents_Type)
; Copy size of data:
NumPut(NumGet(varPtrA + _Var_Capacity_Offset, 0, _Var_Capacity_Type)
    , varPtrB + _Var_Capacity_Offset, 0, _Var_Capacity_Type)
; Set allocation method:
NumPut(_ALLOC_SIMPLE, varPtrB + _Var_HowAllocated_Offset, 0, _Var_HowAllocated_Type)

; Write into varB.
varB := "The quick brown fox jumps."
; Update length of varA.
VarSetCapacity(varA, -1)

MsgBox % "varA: " . varA . "`n @ " . &varA . "`nvarB: " . varB . "`n @ " . &varB

; Write into varB, causing it to auto-expand.
varB := SubStr(varB, 1, -1) . " over the lazy dog."

MsgBox % "varA: " . varA . "`n @ " . &varA . "`nvarB: " . varB . "`n @ " . &varB
It is equally possible to use memory allocated by some other means, however Var::Contents, Capacity and HowAllocated must be set appropriately:
[*:3i432fy9]If the new capacity is 0, Contents should be set to &AnUninitializedVar. Otherwise Contents should be set to the address of the new memory.
[*:3i432fy9]Capacity should be set to the usable size of the new Contents, including the internal zero terminator. VarSetCapacity will report this value minus 1.
[*:3i432fy9]HowAllocated must be set to ALLOC_MALLOC (2) only if AutoHotkey is allowed to pass the memory to its internal copy of free(). In mainstream AutoHotkey, this is limited to memory allocated by an internal call to malloc() or realloc(). __alloc in LowLevel.ahk may be used for this.
[*:3i432fy9]If the new Capacity is 0, HowAllocated may be set to ALLOC_NONE (0), ALLOC_SIMPLE (1) or ALLOC_MALLOC (2). If and when AutoHotkey must expand the variable and the required capacity <= 64 (including null terminator), it may allocate "persistent" memory only if HowAllocated is ALLOC_NONE or ALLOC_SIMPLE.These details are subject to change; they should be accurate for v1.0.48.03.

A similar task can be performed more safely with __alias:
; Expected result: varA and varB show the same value and address both times.

LowLevel_init()

__alias(varB, varA)

varB := "The quick brown fox jumps."
MsgBox % "varA: " . varA . "`n @ " . &varA . "`nvarB: " . varB . "`n @ " . &varB

varB := SubStr(varB, 1, -1) . " over the lazy dog."
MsgBox % "varA: " . varA . "`n @ " . &varA . "`nvarB: " . varB . "`n @ " . &varB


HotKeyIt
  • Moderators
  • 7131 posts
  • Last active: Today, 11:37 AM
  • Joined: 18 Jun 2008
Hi Lexikos,

Thanks for this great feature, I am still trying to learn it but I stuck again and again :(
I am trying to build some dynamic code and can't get further. I found this example from you in some prevoius post and it does not do anything for me?
LowLevel_init()
; Initialize a code generator.
cg := code_gen()

var = 1

; Begin an expression line.
code_line(cg, ACT_EXPRESSION:=3)
; Begin a text/expression arg (expression lines have only one).
code_arg(cg, 0, true)
; Append a function deref/call to the expression.
code_arg_deref(cg, "MyFunc", __findFunc("MyFunc"), true)
; Append the open parenthesis (for valid function-call syntax).
code_arg_write(cg, "(")
; Append a var deref.
code_arg_deref(cg, "var", __getVar(var), false)
; Append the rest of the expression.
code_arg_write(cg, ",2)")
; Finalize the code. Not really necessary in this case, except to use code_run.
ch := code_finalize(cg)
; Call the function.
code_run(ch)
; Delete the code and code generator.
code_delete(ch)
code_gen_delete(cg)

MyFunc(a, b) {
    ListLines
    MsgBox %a%, %b%
}

I am trying to build a function call inside a function, can you help?
Edit: I am using 1.0.48.03.
;Empty function
f(v){
}
;Should result in something like
f(v){
 Return func(v)
}


Lexikos
  • Administrators
  • 9451 posts
  • Last active:
  • Joined: 17 Oct 2006
I've updated my first post to clarify a few compatibility issues.

AutoHotkey v1.0.48:
Dynamic expressions are not supported. AutoHotkey requires expressions to be fully parsed and converted to a postfix-order array of tokens before execution. It is technically feasible but entirely impractical to form an expression using VarSetCapacity and NumPut or similar while referring to the LowLevel documentation and/or AutoHotkey source code.

I am trying to build a function call inside a function, can you help?

Call() demonstrates how that can be done.

HotKeyIt
  • Moderators
  • 7131 posts
  • Last active: Today, 11:37 AM
  • Joined: 18 Jun 2008

AutoHotkey v1.0.48:
Dynamic expressions are not supported. AutoHotkey requires expressions to be fully parsed and converted to a postfix-order array of tokens before execution. It is technically feasible but entirely impractical to form an expression using VarSetCapacity and NumPut or similar while referring to the LowLevel documentation and/or AutoHotkey source code.

So are you saying it is currently not possible in 1.0.48.03 to create/add/insert a line of expression/function call using code.ahk?

Btw. the above code I mentioned crashes when using 1.0.47.06 + LowLevel+Code for 1.0.47.06???

I am trying to build a function call inside a function, can you help?

Call() demonstrates how that can be done.

I tried to change Call() to insert the code into an empty function but no luck :(

When I change
__findLabel("Call_expression_marker")
to
__FindFunc("f")
so in below example func(v) should be inserted in the function, but it crashes when I try to call f(1).
f(v){
    [color=Red]Return func(v)[/color] ;this line needs to be build dynamically
}
func(a){
    return a
}


Lexikos
  • Administrators
  • 9451 posts
  • Last active:
  • Joined: 17 Oct 2006

So are you saying it is currently not possible in 1.0.48.03 to create/add/insert a line of expression/function call using code.ahk?

Interpreting a string such as "F(x+y,z*2)" as an expression requires a lot of work that LowLevel does not do and AutoHotkey cannot be made to do using LowLevel alone. However, simple function calls are feasible as has already been demonstrated earlier in this thread. Please see __curry and adviseUDF.

Btw. the above code I mentioned crashes when using 1.0.47.06 + LowLevel+Code for 1.0.47.06???

Really? It works fine for me. :roll:

I tried to change Call() to insert the code into an empty function but no luck :(

adviseUDF("func", "before", "f") ; "after" works only if the function is not empty.

When I change
__findLabel("Call_expression_marker")
to
__FindFunc("f")

Look at it in context:
arg := NumGet(NumGet(__findLabel("Call_expression_marker"),4),4)
This retrieves the first arg of the line which the label points to. Ordinarily Func and Label are not interchangable, but since JumpToLine is in the same place it will work if the first line of f() actually has an arg to retrieve (e.g. return ("")). However, if the function is empty its first line will be }, which obviously has no args.

Call() uses the local variable 'postfix' to contain the postfix array. For the expression to be safely used after Call returns, you must remove this variable and replace &postfix with a pointer to persistent memory (e.g. returned by GlobalAlloc, or a new static variable as demonstrated by tinku99's __curry).

HotKeyIt
  • Moderators
  • 7131 posts
  • Last active: Today, 11:37 AM
  • Joined: 18 Jun 2008
Thanks Lexikos,

I redownloaded AutoHotkey.exe 1.0.47.6 and examlpe works now :oops:

As far as I understand adviseUDF() and __curry() create an expression.
Then they call code from existing function dynamically by pointing to its line.

As far as I understand I need it different way around.
So I create the code and existing function will run it.

At the moment I do not understand all this postfix/SYM_FUNC token...
I will try to put it together in 1.0.47.6, hopefully you can help me then to transform it to 1.0.48.3 if it is not to complicated :?

HotKeyIt
  • Moderators
  • 7131 posts
  • Last active: Today, 11:37 AM
  • Joined: 18 Jun 2008
Finally the code looks to work as it should in 1.0.47.6.

Can you give me a hint what lines are not compatible to 1.0.48.3 and possibly what I have to do instead to get it to work in 1.0.48.3?

LowLevel_init()

; Initialize a code generator.
cg := code_gen()

;function to redirect code
func_ptr:=__findFunc("func")

; Begin an expression line.
line_ptr:=code_line(cg, ACT_RETURN:=102)

; Change JumpToLine of the function
NumPut(line_ptr+0,func_ptr+4,"UInt")


code_arg(cg, 0, true) ; Begin a text/expression arg (expression lines have only one).
code_arg_deref(cg, "MyFunc", __findFunc("MyFunc"), true,7) ; Append a function deref/call to the expression.
code_arg_write(cg, "(") ; Append the open parenthesis (for valid function-call syntax).
code_arg_deref(cg, "A_ThisFunc", __getBuiltInVar("A_ThisFunc"), false) ; Append BuildIn Variable A_ThisFunc as first parameter
code_arg_write(cg, ","""","""","""","""",9223372036854775807,") ; Append parameters 2-6
code_arg_deref(cg, "v", NumGet(NumGet(func_ptr+8)+0), false) ; Append a var deref.
code_arg_write(cg, ")") ; Append the rest of the expression.

; Finalize the code. Not really necessary in this case, except to use code_run.
ch := code_finalize(cg)
code_delete_handle(ch) ; Delete the code handle but not the code
code_gen_delete(cg) ; Delete the code generator.


; Call the function.
MsgBox % func("param")

ExitApp

func(v=""){
}

MyFunc(a="",b="",c="",d="",e="",f="",g="") {
    ListVars 
    return g
}


#include x:\code.ahk
#include x:\LowLevel.ahk


Lexikos
  • Administrators
  • 9451 posts
  • Last active:
  • Joined: 17 Oct 2006

As far as I understand adviseUDF() and __curry() create an expression.
Then they call code from existing function dynamically by pointing to its line.

No. adviseUDF inserts a line containing a function call into an existing function. __curry creates a new function containing a function call. They do not run the code.

Can you give me a hint what lines are not compatible to 1.0.48.3

Anything related to dynamic expressions will not work, so code_arg(cg, 0, true) and any subsequent code_arg_* calls.

and possibly what I have to do instead to get it to work in 1.0.48.3?

I have already gone over it more than once.