Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trouble identifying end-of-label-block during label-to-function conversion #236

Open
andymbody opened this issue Jul 7, 2024 · 16 comments

Comments

@andymbody
Copy link
Contributor

andymbody commented Jul 7, 2024

V1:

gosub Label1

MsgBox, % "global msg"
Return

Label1:
var := 5
if var < 5
    return   ; this return can fool any attempt to detect end of block
MsgBox, label/func msg
Return    ; final return should be used to detect end of label

MsgBox Not part of Label1

V2 (Converted):

Label1()

MsgBox("global msg")
Return

Label1()
{ ; V1toV2: Added bracket
global ; V1toV2: Made function global
var := 5
if (var < 5)
    return   ; this return can fool any attempt to detect end of block
MsgBox("label/func msg")
Return    ; final return should be used to detect end of label

MsgBox("Not part of Label1")
} ; Added bracket in the end

V2 (Expected):

Label1()

MsgBox("global msg")
Return

Label1()
{ ; V1toV2: Added bracket
global ; V1toV2: Made function global
var := 5
if (var < 5)
    return   ; this return can fool any attempt to detect end of block
MsgBox("label/func msg")
Return    ; final return should be used to detect end of label
} ; Added closing brace to func

MsgBox("Not part of Label1")

I ran into this while working on label name conversions (current project). I played with it and see that the labelToFunc conversion relies on detecting the beginning of hotkeys/hotstrings/functions to know where the previous block ends. This is very unreliable and should be fixed. I plan to tackle this issue next (after the label/func names).

Looks like the fix will require dipping into the masking bucket. I will need to add masking support for other blocks such as If/While/Loop/Switch, etc... Anything that can redirect the target label/func block using return/exitapp/ etc. Masking the sub-blocks temporarily should hide those conditional redirect commands and allow the the lableToFunc to reliably identify the final return/exitapp/exit within the label/func block. I have already have a very long If block needle and it works well for standard If/If-Else/Else, but will need to make sure all legacy IFs have been handle first. The needle handles most of the legacy IFs as well but I recently ran into a couple that were not detected. Might need to write a callout function that compliments the needle, or a dedicated function to make things easier. We will see how it goes.

@mmikeww
Copy link
Owner

mmikeww commented Jul 7, 2024

i imagine this will be difficult

i dont even like the existing conversion of label to function, because labels were not really "subroutines" despite the gosub syntax

@andymbody
Copy link
Contributor Author

i imagine this will be difficult

It will be a task. I played with it awhile and nearly re-wrote that entire function (lol) and had it all working fine, until I ran into the if block on one of the tests. That's when I realized masking will be required. I think once that is applied, it should be much easier and cleaner conversion function. I could be wrong... we will see. If it wasn't a challenge, it wouldn't be any fun. lol.

@fade2gray
Copy link
Contributor

fade2gray commented Jul 8, 2024

Just thinking out loud.

Couldn't the converter be made to format the V1 code, e.g. ...

gosub Label1

MsgBox, % "global msg"
Return

Label1:
    var := 5
    if var < 5
        return   ; this return can fool any attempt to detect end of block
    MsgBox, label/func msg
Return    ; final return should be used to detect end of label

MsgBox Not part of Label1

This would make it easier to find the final return for the label?

@mmikeww
Copy link
Owner

mmikeww commented Jul 8, 2024

a final return isnt even necessary i dont think.. the code could theoretically run to the end of the file

and what about situations like this

Label1:
    var := 5
Label2:
    if var < 5
        return   ; this return can fool any attempt to detect end of block
    MsgBox, label/func msg
Return    ; final return should be used to detect end of label

@fade2gray
Copy link
Contributor

fade2gray commented Jul 8, 2024

Lable1 drops through to Label2, so you are still only looking for the final return for both labels..

@mmikeww
Copy link
Owner

mmikeww commented Jul 8, 2024

yes i know that, but i havent tried the existing conversion yet, but it seems the converter tries to convert labels to funcs

does this end up as a nested func?

gosub label1
gosub label2
return

Label1:
    var := 5
Label2:
    if var < 5
        return   ; this return can fool any attempt to detect end of block
    MsgBox, label/func msg
Return    ; final return should be used to detect end of label

@fade2gray
Copy link
Contributor

To maintain the same functionality as the V1 code, the algorithm to produce the V2 conversion would have to be somehow adjusted to produce ...

Label1()
Label2()

MsgBox("global msg")
Return

Label1()
{ ; V1toV2: Added opening brace
    global ; V1toV2: Made function global
    var := 5
    Label2()
} ; V1toV2: Added closing brace

Label2()
{ ; V1toV2: Added opening brace
    global ; V1toV2: Made function global
    if (var < 5)
        return   ; this return can fool any attempt to detect end of block
    MsgBox("label/func msg")
    Return    ; final return should be used to detect end of label
} ; V1toV2: Added closing brace

@andymbody
Copy link
Contributor Author

andymbody commented Jul 8, 2024

does this end up as a nested func?

Just tried it... it does become nested functions.

I have finished the label names fix (#238 unless one of you find an error - lol). I don't have power right now due to a storm but will dig into this soon. Should be interesting. But this will need to build on top of the label fix (once it is approved).

@Banaanae
Copy link
Collaborator

Banaanae commented Jul 9, 2024

GoSub MyFunc()
MsgBox, C
GoSub MyFunc2()

MyFunc():
MsgBox, A
MyFunc2():
MsgBox, B
Return

Handling this one should be fun
Output is A > B > C > B > A > B

@mmikeww
Copy link
Owner

mmikeww commented Jul 9, 2024

lmao

@Banaanae
Copy link
Collaborator

Alternative conversion that fixes falling in problems

GoSub, LabelA
MsgBox, After Label

LabelA:
MsgBox, In Label
Return
In_LabelA := true
Goto("LabelA")
After_LabelA:
MsgBox("After Label")

LabelA:
MsgBox("In Label")
if (In_LabelA = true) {
In_LabelA := false
Goto("After_LabelA")
}

It isn't the prettiest solution, but behaves identically (atleast in my testing)

@mmikeww
Copy link
Owner

mmikeww commented Jul 10, 2024 via email

@mmikeww
Copy link
Owner

mmikeww commented Jul 10, 2024 via email

@andymbody
Copy link
Contributor Author

Had to take a break from coding... hope to get back to this issue soon.

@andymbody
Copy link
Contributor Author

andymbody commented Aug 14, 2024

GoSub MyFunc()
MsgBox, C
GoSub MyFunc2()

MyFunc():
MsgBox, A
MyFunc2():
MsgBox, B
Return

Handling this one should be fun Output is A > B > C > B > A > B

I was thinking something like this might be easier...

CombineLabelFunc1()	; redirect when referencing a top-most label
MsgBox("C")
MyFunc2__()

CombineLabelFunc1()	; redirect when referencing a top-most label
MyFunc__()
{ ; V1toV2: Added bracket
	global ; V1toV2: Made function global
	MsgBox("A")
} ; V1toV2: Added bracket in end of label (before next detected block)

MyFunc2__()
{ ; V1toV2: Added bracket
	global ; V1toV2: Made function global
	MsgBox("B")
	Return
} ; V1toV2: Added bracket in end of label (before next detected block)

CombineLabelFunc1()	; redirected to here
{
	myFunc__()
	MyFunc2__()
}

Should be easy to detect, and easy to implement, with no side-effects, and fairly clean. Although I have not fully tested all scenarios yet.

GoSub MyFunc()
MsgBox, C
GoSub MyFunc2()

MyFunc():
MsgBox, A
MyFunc2():
MsgBox, B
MyFunc3():
MsgBox, D
Return
; redirect when referencing a label that falls thru to other labels
CombineLabelFunc1()
MsgBox("C")
; redirect when referencing a label that falls thru to other labels
CombineLabelFunc2()

; redirect when referencing a label that falls thru to other labels
CombineLabelFunc1()
MyFunc__()
{ ; V1toV2: Added bracket
	global ; V1toV2: Made function global
	MsgBox("A")
} ; V1toV2: Added bracket in end of label (before next detected block)

MyFunc2__()
{ ; V1toV2: Added bracket
	global ; V1toV2: Made function global
	MsgBox("B")
} ; V1toV2: Added bracket in end of label (before next detected block)

MyFunc3__()
{ ; V1toV2: Added bracket
	global ; V1toV2: Made function global
	MsgBox("D")
	Return
} ; V1toV2: Added bracket in end of label (before next detected block)

CombineLabelFunc1()	; redirected to here
{
	myFunc__()
	MyFunc2__()
	MyFunc3__()
}

CombineLabelFunc2()	; redirected to here
{
	MyFunc2__()
	MyFunc3__()
}

A > B > D > C > B > D > A > B > D

@Lexikos
Copy link

Lexikos commented Nov 23, 2024

You can't replace gosub with goto. Subroutines utilize a stack, with the return point (the line which called the subroutine) being variable. The only sane replacement for a label-based subroutine is a non-label-based subroutine: a function.

Fall-through can often be viewed as an implicit tail call to the subroutine, since by definition, the subroutine ends with Return.

GoSub, LabelA
MsgBox, After Label
GoSub, LabelA ; Implied
Return ; Implied
LabelA:
MsgBox, In Label
Return
LabelA()
MsgBox "After Label"
LabelA()
Return
LabelA() {
    MsgBox "In Label"
}

If A_ThisLabel was used to determine which label execution originally started at, it can be replaced by a function parameter used by the implicit tail calls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants