Skip to content

Conversation

@Almesi
Copy link
Contributor

@Almesi Almesi commented Dec 8, 2025

Added Support for for and do Loops

do Loops

The do loop works by creating two jump operations:

  1. First jump: Exits the loop if the condition is met.
  2. Loop body: Executes the statements inside the loop.
  3. Second jump: Returns to the condition check.

for Loops

The for loop works by creating two jump operations and incrementing an index variable:

  1. Initialization: Assign the first value to the index.
  2. Condition check.
  3. First jump: Exits the loop if the condition is met.
  4. Loop body: Executes the loop statements.
  5. Increment: Updates the index variable.
  6. Second jump: Returns to the condition check.

Notes on Loop Implementation

  • Looping is typically implemented by creating a variable in memory and incrementing or modifying it.
  • Since this implementation is not recursive, the position of the variable on the stack is critical for the loops to work correctly.
  • Stack size constraints: The stack must not grow or shrink during loop execution. To enforce this, I added an InLoop flag to some statements that always return a value.

Why InLoop is needed:

  • Statements that always return a value will push something onto the stack every loop iteration.
  • If the stack grows while the loop runs, the IAccess_General still accesses the stack at the same offset, returning new values each iteration.
  • This behavior can lead to errors or infinite loops in most cases, errors in other cases.
  • InLoop acts as a guard, checking whether the statement is called inside an active loop, preventing stack growth issues.

Note: This is a rather hacky solution, if anyone has any tips on how to solve that feel free to write.

Other Alternatives i tried

  1. Added Pop after every Variable assignment in Loop to get rid of extra IAccess_General
    • Issue: Once you actually want Access the value this method will remove it
    Let x = 0
    Let i = 0
    Let j = 0
    For i = 1 To Index
        Let j = 1 '<-- That would be the case here: Push-->set-->access-->pop-->access will result in stack offset
        Do Until j = Index
            Let x = x + 1
            Let j = j + 1
        Loop
    Next
    Let res = x
  1. Added new functionality called iAccess_Dynamic, which would access values not according to a static value, but based on the previous stack´s value
    • Issue: lots of overhead and messy code for a niche problem
    00               Push               5 (variable "x")                    ' |
    01               Push               0 (variable "i")                    ' \Assigning Values
    02               Access             1                                   ' |
    03               Push               10                                  ' |
    04               Compare            LessThan                            ' |
    05               JumpIfFalse        23                                  ' \Loop Condition
    06               Access             2                                   ' |
    07               Push               5                                   ' |
    08               Add                                                    ' |
    09               Set                2                                   ' \Do Something in Loop, this case increment x
    10               Access             2                                   ' |<--Side Effect of always returning something
    12               GetAddress         "i"                                 ' |
    11               Push               StackSize                           ' |
    12               Subtract                                               ' |
    15               AccessDynamic      (result would be 3 - 1 = 2)         ' |
    16               Push               1                                   ' |
    17               Add                                                    ' \Increment i
    18               Push               StackSize                           ' |
    19               GetAddress         "i"                                 ' |
    19               Subtract           -1                                  ' |
    21               SetDynamic         (result would be 3 - 1 = 2)         ' \Update i
    22               Jump               2                                   ' |
    23               OutSide                                                ' |

This example wont even work, because x will still increase the stack and the system would be offset +1 every iteration, but i think the example shows,
how this will bloat the code dramatically

  1. Changing the tokens before parsing

    • Issue: previously discussed, this approach was not favoured in the spirit of this class
  2. Trying a revursive approach

    • Issue: way to limiting in choices, as you do not have access to the stack properly. Also, recursive approach is implementable already if the user wishes so.

Limitations:

  • No implementation of an Exit keyword yet.
  • Prematurely leaving a do, for, or fun block can misalign the stack, causing errors down the line.

Test Cases

' If result is 0, then both stdlambda and normal code ran the same way
Public Sub Testing()
    Dim i As Long
    For i = 0 To 20
        Debug.Print i & "-------------------------------"
        'Debug.Print "TestExitDo                                         " & i & ": " & (TestExitDo(i)                          - TestExitDoShouldBe(i))
        'Debug.Print "TestExitFor                                        " & i & ": " & (TestExitFor(i)                         - TestExitForShouldBe(i))
        'Debug.Print "TestExitFun                                        " & i & ": " & (TestExitFun(i)                         - TestExitFunShouldBe(i))
        Debug.Print "TestForLoop                                        " & i & ": " & (TestForLoop(i)                         - TestForLoopShouldBe(i))
        Debug.Print "TestIfInUbound                                     " & i & ": " & (TestIfInUbound(i)                      - TestIfInUboundShouldBe(i))
        Debug.Print "TestParantInUbound                                 " & i & ": " & (TestParantInUbound(i)                  - TestParantInUboundShouldBe(i))
        Debug.Print "TestExprInUbound                                   " & i & ": " & (TestExprInUbound(i)                    - TestExprInUboundShouldBe(i))
        Debug.Print "TestForLoopPredeclared                             " & i & ": " & (TestForLoopPredeclared(i)              - TestForLoopPredeclaredShouldBe(i))
        Debug.Print "TestFunctionInForLoop                              " & i & ": " & (TestFunctionInForLoop(i)               - TestFunctionInForLoopShouldBe(i))
        'Debug.Print "TestFunctionInForLoop2                             " & i & ": " & (TestFunctionInForLoop2(i)              - TestFunctionInForLoop2ShouldBe(i))
        Debug.Print "TestFunctionInDoLoop                               " & i & ": " & (TestFunctionInDoLoop(i)                - TestFunctionInDoLoopShouldBe(i))
        Debug.Print "TestFunctionInDoLoopWithWhileAtEnd                 " & i & ": " & (TestFunctionInDoLoopWithWhileAtEnd(i)  - TestFunctionInDoLoopWithWhileAtEndShouldBe(i))
        'Debug.Print "TestFunctionInDoLoopExit                           " & i & ": " & (TestFunctionInDoLoopExit(i)            - TestFunctionInDoLoopExitShouldBe(i))
        Debug.Print "TestNestedLoop                                     " & i & ": " & (TestNestedLoop(i)                      - TestNestedLoopShouldBe(i))
        Debug.Print "TestNestedForLoopAndDoLoop                         " & i & ": " & (TestNestedForLoopAndDoLoop(i)          - TestNestedForLoopAndDoLoopShouldBe(i))
    Next i
End Sub

'  "Exit" not implemented as of now because it will throw off the stack by returning early
Private Function TestExitDo(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "Let x = 0", _
        "Let i = 0", _
        "Do While i < $1", _
            "If x < 10 Then", _
                "Let x = 2 + i", _
            "Else", _
                "Exit Do", _
            "End", _
            "let i = i + 1", _
        "Loop", _
        "Let res = x"))
    TestExitDo = Lambda.Run(Index)
End Function

'  "Exit" not implemented as of now because it will throw off the stack by returning early
Private Function TestExitFor(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "Let x = 0", _
        "For i = 1 To $1", _
        "    If x < 10 Then", _
        "        Let x = 2 + i", _
        "    Else", _
        "        Exit For", _
        "    End", _
        "Next", _
        "Let res = x"))
    TestExitFor = Lambda.Run(Index)
End Function

'  "Exit" not implemented as of now because it will throw off the stack by returning early
Private Function TestExitFun(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
    "Fun func(v)", _
        "v * 3", _
        "Exit Fun", _
        "v * 4", _
    "End", _
    "Let res = func($1)"))
    TestExitFun = Lambda.Run(Index)
End Function

Private Function TestForLoop(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "let x = 0", _
        "for i = 1 To $1", _
        "   let x = 2 + i", _
        "next", _
        "let res = x"))
    TestForLoop = Lambda.Run(Index)
End Function

Private Function TestIfInUbound(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "let x = 100", _
        "For i = 0 to If x > 10 Then 10 Else 20 End Step +2", _
        "  let x = x / 2", _
        "next", _
        "let res = x"))
    TestIfInUbound = Lambda.Run(Index)
End Function

Private Function TestParantInUbound(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "let x = $1", _
        "let i = 0", _
        "For i = 0 to (10+2) Step +2", _
        "  let x = x / 2", _
        "next", _
        "let res = x"))
    TestParantInUbound = Lambda.Run(Index)
End Function

Private Function TestExprInUbound(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "let x = $1", _
        "let i = 0", _
        "For i = 0 to 10+2 Step +2", _
        "    let x = x / 2", _
        "next", _
        "let res = x"))
    TestExprInUbound = Lambda.Run(Index)
End Function

Private Function TestForLoopPredeclared(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "let i = 0", _
        "let x = 0", _
        "for i = 1 To $1", _
        "   let x = 2 + i", _
        "next", _
        "let res = x"))
    TestForLoopPredeclared = Lambda.Run(Index)
End Function

Private Function TestFunctionInForLoop(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "let x = 0", _
        "fun fib(v)", _
        "    if v<=1 then", _
        "        v", _
        "    else ", _
        "         fib(v-2) + fib(v-1)", _
        "    end", _
        "end", _
        " ", _
        "for i = 1 To 10", _
        "   let x = x + fib($1)", _
        "next", _
        "let res = x"))
    TestFunctionInForLoop = Lambda.Run(Index)
End Function

' This will not work, as x is not defined previously
' meaning the moment lambda tries to do let x = x + duuble($1) it will view the second x as "x", since it wasnt initialised before
Private Function TestFunctionInForLoop2(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "fun duuble(v)", _
        "    let v * 2", _
        "end", _
        " ", _
        "For i = 1 To $1", _
        "    let x = x + duuble($1)", _
        "next", _
        "let res = x"))
    TestFunctionInForLoop2 = Lambda.Run(Index)
End Function

Private Function TestFunctionInDoLoop(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "let x = 1", _
        "let i = 0", _
        "fun duuble(v)", _
        "    let v * 2", _
        "end", _
        " ", _
        "Do While i < $1", _
        "    let i = i + 1", _
        "    let x = x + duuble($1)", _
        "loop", _
        "let res = x"))
    TestFunctionInDoLoop = Lambda.Run(Index)
End Function

Private Function TestFunctionInDoLoopWithWhileAtEnd(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "let x = 1", _
        "let i = 0", _
        "fun duuble(v)", _
        "    let v * 2", _
        "end", _
        " ", _
        "Do", _
        "    let i = i + 1", _
        "    let x = x + duuble($1)", _
        "loop  While i < $1", _
        "let res = x"))
    TestFunctionInDoLoopWithWhileAtEnd = Lambda.Run(Index)
End Function

'  "Exit" not implemented as of now because it will throw off the stack by returning early
Private Function TestFunctionInDoLoopExit(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "let x = 1", _
        "let i = 0", _
        "Do", _
        "    let i = i + 1", _
        "    if $1 > 10 then Exit do end", _
        "    let x = x * 2", _
        "loop While i < $1", _
        "let res = x"))
    TestFunctionInDoLoopExit = Lambda.Run(Index)
End Function

Private Function TestNestedLoop(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "let x = 0", _
        "let i = 0", _
        "let j = 0", _
        "for i = 1 To $1", _
        "    for j = 1 To $1", _
        "        let x = x + 1", _
        "    next", _
        "next", _
        "let res = x"))
    TestNestedLoop = Lambda.Run(Index)
End Function

Private Function TestNestedForLoopAndDoLoop(Index As Long)
    Static Lambda As stdLambda
    If Lambda Is Nothing Then Set Lambda = stdLambda.CreateMultiline(Array( _
        "let x = 0", _
        "let i = 0", _
        "let j = 0", _
        "for i = 1 To $1", _
        "    let j = 1", _
        "    let x = 1", _
        "    do until j = $1", _
        "        let x = x + 1", _
        "        let j = j + 1", _
        "    loop", _
        "next", _
        "let res = x"))
    TestNestedForLoopAndDoLoop = Lambda.Run(Index)
End Function






'===========================================================================
'===============================Actual VBA Code=============================
'===========================================================================


Private Function TestExitDoShouldBe(Index As Long) As Double
    Let x = 0
    Let i = 0
    Do While i < Index
        If x < 10 Then
            Let x = 2 + i
        Else
            Exit Do
        End If
        i = i + 1
    Loop
    Let res = x
    TestExitDoShouldBe = res
End Function

Private Function TestExitForShouldBe(Index As Long) As Double
    Let x = 0
    For i = 1 To Index
        If x < 10 Then
            Let x = 2 + i
        Else
            Exit For
        End If
    Next
    Let res = x
    TestExitForShouldBe = res
End Function

Private Function TestExitFunShouldBe(Index As Long) As Double
    TestExitFunShouldBe = func(Index)
End Function

Private Function TestForLoopShouldBe(Index As Long) As Double
    Let x = 0
    For i = 1 To Index
        Let x = 2 + i
    Next
    Let res = x
    TestForLoopShouldBe = res
End Function

Private Function TestIfInUboundShouldBe(Index As Long) As Double
    Let x = 100
    Do While i <= IIf(x > 10, 10, 20)
        Let x = x / 2
        i = i + 2
    Loop
    Let res = x
    TestIfInUboundShouldBe = res
End Function

Private Function TestParantInUboundShouldBe(Index As Long) As Double
    Let x = Index
    Let i = 0
    For i = 0 To (10 + 2) Step 2
        Let x = x / 2
    Next
    Let res = x
    TestParantInUboundShouldBe = res
End Function

Private Function TestExprInUboundShouldBe(Index As Long) As Double
    Let x = Index
    Let i = 0
    For i = 0 To 10 + 2 Step 2
        Let x = x / 2
    Next
    Let res = x
    TestExprInUboundShouldBe = res
End Function

Private Function TestForLoopPredeclaredShouldBe(Index As Long) As Double
    Let i = 0
    Let x = 0
    For i = 1 To Index
        Let x = 2 + i
    Next
    Let res = x
    TestForLoopPredeclaredShouldBe = res
End Function

Private Function TestFunctionInForLoopShouldBe(Index As Long) As Double
    Let x = 0
    For i = 1 To 10
        Let x = x + fib(Index)
    Next
    Let res = x
    TestFunctionInForLoopShouldBe = res
End Function

' This will not work, as x is not defined previously
' meaning the moment lambda tries to do let x = x + duuble(Index) it will view the second x as x, since it wasnt initialised before
Private Function TestFunctionInForLoop2ShouldBe(Index As Long) As Double
    For i = 1 To Index
        Let x = x + duuble(Index)
    Next
    Let res = x
    TestPrivate FunctionInForLoop2ShouldBe = res
End Function

Private Function TestFunctionInDoLoopShouldBe(Index As Long) As Double
    Let x = 1
    Let i = 0
    Do While i < Index
        Let i = i + 1
        Let x = x + duuble(Index)
    Loop
    Let res = x
    TestFunctionInDoLoopShouldBe = res
End Function

Private Function TestFunctionInDoLoopWithWhileAtEndShouldBe(Index As Long) As Double
    Let x = 1
    Let i = 0
    Do
        Let i = i + 1
        Let x = x + duuble(Index)
    Loop While i < Index
    Let res = x
    TestFunctionInDoLoopWithWhileAtEndShouldBe = res
End Function

Private Function TestFunctionInDoLoopExitShouldBe(Index As Long) As Double
    Let x = 1
    Let i = 0
    Do
        Let i = i + 1
        If Index > 10 Then Exit Do
        Let x = x * 2
    Loop While i < Index
    Let res = x
    TestFunctionInDoLoopExitShouldBe = res
End Function

Private Function TestNestedLoopShouldBe(Index As Long) As Double
    Let x = 0
    Let i = 0
    Let j = 0
    For i = 1 To Index
        For j = 1 To Index
            Let x = x + 1
        Next
    Next
    Let res = x
    TestNestedLoopShouldBe = res
End Function

Private Function TestNestedForLoopAndDoLoopShouldBe(Index As Long) As Double
    Let x = 0
    Let i = 0
    Let j = 0
    For i = 1 To Index
        Let j = 1
        Let x = 1
        Do Until j = Index
            Let x = x + 1
            Let j = j + 1
        Loop
    Next
    Let res = x
    TestNestedForLoopAndDoLoopShouldBe = res
End Function

Private Function duuble(v) As Double
    duuble = v * 2
End Function

Private Function fib(v) As Long
    If v <= 1 Then
        fib = v
    Else
        fib = fib(v - 2) + fib(v - 1)
    End If
End Function

Private Function func(v)
    func = v * 3
    Exit Function
    func = v * 4
End Function

No implementation of exit keyword as of now. That is because premature leaving of either do, for or fun can result in misaligned stack, which will cause problems down the line.
@sancarn
Copy link
Owner

sancarn commented Dec 9, 2025

@Almesi this looks much better! I definitely prefer the use of InLoop albeit still seemingly hacky, but this at least limits the impact of these changes to only new code, which I think is sufficiently safe for this PR 👍 Thanks again for resubmitting and I apologies I never got around to changing your previous PR!

@sancarn sancarn merged commit c861788 into sancarn:master Dec 9, 2025
1 check passed
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

Successfully merging this pull request may close these issues.

2 participants