Aren't you just pushing the open construct onto a LIFO/push-down stack and popping it off when a matching close construct is found?

If so it should be trivial to make the stacked item a compound of the open construct and the source line number.

No issue with nested counts either, other than memory/string limits for the stack size.

From your example:
 Code:
000001: Main
000005:  If $X = 1
000007:  EndIf
000009:  If $X = 1
000010:   While $X < 2
000011:    If $X = 1
000013:    Loop
 - WARNING: Unterminated If when closing While!


If you are stacking the constructs then your stack would look like this at the point that you hit the Loop:
 Code:
[0] 00011:IF
[1] 00010:WHILE
[2] 00009:IF


This makes it easy to track back and issue a message like:
 Code:
000001: Main
000005:  If $X = 1
000007:  EndIf
000009:  If $X = 1
000010:   While $X < 2
000011:    If $X = 1
000013:    Loop
 - WARNING: Loop without While
            Possible unterminated "IF" at line 000011
            Possible match with "WHILE" at line 000010