Author Topic: Declaring variables inside a switch/case statement  (Read 525 times)

Legacy_Squatting Monk

  • Hero Member
  • *****
  • Posts: 776
  • Karma: +0/-0
Declaring variables inside a switch/case statement
« on: August 25, 2015, 08:42:09 am »


               
According to the Lexicon, you cannot declare or initialize variables within a switch/case statement. I seem to remember this being a problem for me in the past, but that you could declare variables within a case statement if you wrapped in in curly braces:



// This wouldn't work
case 1:
    int i = 0;
    break;
 
// This would
case 2:
{
    int i = 0;
} break;


However, I find that I can both declare and initialize variables within a case statement regardless of whether I use curly braces. Is this an effect of the different compiler (I am using Skywing's compiler as it ships with NWNTX), or was this behavior fixed in one of the patches?


               
               

               
            

Legacy_Proleric

  • Hero Member
  • *****
  • Posts: 1750
  • Karma: +0/-0
Declaring variables inside a switch/case statement
« Reply #1 on: August 25, 2015, 09:42:54 am »


               

It works with the Bioware compiler, so I imagine it was fixed.



               
               

               
            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
Declaring variables inside a switch/case statement
« Reply #2 on: August 26, 2015, 12:03:28 am »


               

It is the  effect of the different compiler.   You can not declare variables in the case statement with the standard compiler.   

 

I am a little surprised the skywings compiler allows it.    But I guess is a glass half full/ half empty type of thing if you are counting the number of instructions executed to avoid TMI errors.   

 

 

The only way I can see skywings compiler allowing it, is if all the variables you are declaring in the script ( or at least the ones you are declaring in the switch )  are having stack space reserved  for them   regardless of program flow.   .  

 

 

The reason the declaration does not work, is the same reason this 

 

 if (a) int b;

 

does not work.   

 

but this 

 

 if (a) {int b;}

 

does.  

 
Detailed Answer

 

In the standard nwn compiler space is reserved  for the variable when the declaration is executed in the code.   The memory/ stack is freed/cleaned  at the end of the name space or when the variable no longer has scope.   

 

When the variable is accessed in the code the memory location that it represents is found via an offset from either the Top Of the Stack  or a base pointer  that points to a know point in the stack.  The Top Of Stack pointer (TOS) points at the top of the stack so that everything above it currently unused and everything below where it is pointing is being used.   

  

 

For simplicity   lets just consider the stack as an array of dwords that already had enough memory allocated to it to hold what every we want.  And the TOS pointer having the index of the first unused dword.  

 

 

So given the following code fragment.

 

 



int a;
int b = 5;
a= GetSwitchvalue();
switch(a)
  case 1:
    int c;  
  case 2:
    { // something or other.}
  default:
    {//  some randon code}
  b=1; 

 

 
line 1: : int a;


  when the 'int a;'  is executed the only thing that happens is the TOS pointer is increased by 1.    So if it was currently 54 it is now 55  but we really have no Idea where it's starting point is.     So the variable  'a' at this point in the code would be in the index  of TOS -1    concidering our stack as an array we would have STACK[TOS -1]  but this will change with every thin that happens with the stack.  


 


 


line 2: int b =5;


 The TOS is increased by one again to reserve space for the b variable.   Then it is increased again to push the constant 5 onto the top of the stack.   So at this mid stage we have:


 


reserved space for (var a) at TOS -3


reserved space for (var 'B)' at TOS -2  and


the constant 5   stored at TOS  -1 


 


the assignment operator moves whatever is on the top of the stack (TOS-1)  to an offset from the TOS and decreases the TOS by one.     In this case the offset  to move the top of the stack to would be -2  because that is where we know the reserved space for var b is at.  


 


So at the point line 2 is finished  var b is at TOS-1  and var a is at TOS-2.   even though they where not constantly in the same offsets during even the execution of the line. 


 


line 3:  a = GetSwitchvalue();  


 


The execution of this statement is going to start with the execution of the function.  assuming that it compiled we can tell that the function is an int returning function.  code from somewhere else is going to execute and the stack pointer is going to go up and down and who know what.   but this is know.   once the code for the function is finished the stack pointer is going to be increased by one with a new integer on the top of the stack that was returned by the function.    (  This by the way is why the function header is needed before  the function can be used in the code but not necessarily  the entire function body.  at this point in the compiling process only the net result to the stack needs to be known, to keep track of the vars,  and not where or what the function is.  that will be filled in with another pass of the compiler.)


 


Stack after the function looks like.  


TOS-1  returned integer  from the  GetSwitchvalue() function.  


TOS-2  storage for var  b 


TOS-3  Storage for var  a 


 


to finish off the statement only the assignment need to take place.   


TOS-1 is moved to TOS-3 and the Stack is reduced by 1 to trim the return value from the stack.  


 


So the stack is once again at 


 


TOS-1  storage for b  with 5 stored in it from the line 2. 


TOS-2  Storage for a    with the new return value now stored in it.  


 


Line 4:  switch(a)


 


with the execution of this control structure,  the value Var-a at  known offset of TOS-2  is going to be pushed to the top of the stack some  comparisons are going to be made to decide where execution continues at.  the top of the stack will be popped off   and a jump in execution will will be made to either line 6,8 or 10. .   


 


 


Line 5,7 and 9: case 1; case 2: and default:   


 


No code is injected for any of these lines they simply reference points to the compiler for a future pass when it fills in the jump offsets for the switch statement.


 


Line 6:  int c; 


 


The TOS is increased by one with storage reserved for Var-c


 


Skipping down to line 11. 


 


Line 11: b=1;


 


ok we move the constant 1 to the top of the stack.  


 


and move that value to the offset where b is at....  Hmmm.  


 


did line 6 execute?  


 


Is the stack  currently.  


TOS -1  The constant one just pushed here.


TOS-2   c from line 6 ?? or is it var b ??....


 


So at this point the compiler need to move the top of the stack to either  TOS  -2 if line 6 did not execute or TOS-3 if it did execute.   since the offset need to be known at the time of compiling and can not change this thrown a major fly into the ointment.   


 


 


So you may ask why would {int c;}  work where int c; does not.  The answer is rather simple.  The } declares the end of the name space.   c simply has no scope outside of the {}  once the closing }  all vars that where created within that block of code are removed from the stack, leaving the stack in the same state it was in before the opening {


 


Hope no one minds how long winded I got.  


 


L8  


   


 


 



               
               

               
            

Legacy_Squatting Monk

  • Hero Member
  • *****
  • Posts: 776
  • Karma: +0/-0
Declaring variables inside a switch/case statement
« Reply #3 on: August 26, 2015, 07:30:38 am »


               

Ah, yes, I know some of those words.


 


In all seriousness, though, thanks for taking the time to explain. I don't know much programming outside of NWScript, so it's helpful to have someone knowledgeable explain the inner workings.



               
               

               
            

Legacy_Proleric

  • Hero Member
  • *****
  • Posts: 1750
  • Karma: +0/-0
Declaring variables inside a switch/case statement
« Reply #4 on: August 26, 2015, 09:10:02 am »


               

@Lightfoot8 - "You can not declare variables in the case statement with the standard compiler."


 


Well, it doesn't raise a compilation error. Are you saying that the code compiles, but doesn't work?



               
               

               
            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
Declaring variables inside a switch/case statement
« Reply #5 on: August 26, 2015, 11:28:22 am »


               


@Lightfoot8 - "You can not declare variables in the case statement with the standard compiler."


 


Well, it doesn't raise a compilation error. Are you saying that the code compiles, but doesn't work?




 


 


The compiler will complain.   Either you have updated the compiler or are using the { and }  around your cases to fix the problem.   


               
               

               
            

Legacy_meaglyn

  • Hero Member
  • *****
  • Posts: 1451
  • Karma: +0/-0
Declaring variables inside a switch/case statement
« Reply #6 on: August 26, 2015, 01:53:29 pm »


               

Proleric, are you doing that in an include or an executable script? In my toolset the first pass succeeds (syntactically it is allowed by the language definition). It then fails with an explicit error in the later passes, which only happen when it is a "real" script. So it works in an include which only goes through the first pass of the compiler.


 


Lightfoot, thanks for the details. That seems to be a "feature" specifically of the naive code generation used in the official compiler. The grammar for NWscript (I used to have a link to a post by one of the guys who wrote the compiler which included the grammar but I can't seem to find it and google no longer does either...) allows this.  This can be seen by the fact that it passes the initial phase of the compilation (it works in an include file).  This can certainly be implemented (and it is in skywing's and other out-of-box compilers, but it requires a lot more complex code generation around the case statements.  It is not an inherent limitation of the stack base VM.  I suspect the bioware team just decided to punt on that complexity and so made it throw an error if it hit that case, despite it being legal in the formal language definition.



               
               

               
            

Legacy_Squatting Monk

  • Hero Member
  • *****
  • Posts: 776
  • Karma: +0/-0
Declaring variables inside a switch/case statement
« Reply #7 on: August 27, 2015, 08:31:59 am »


               

Further testing with Skywing's compiler yields the following error when I do not enclose a variable declaration with curly braces:



Error: NSC1066: Variable declaration skipped by "case" statement (consider enclosing declaration in braces)

It appears my original tests were not actually calling the function that contained my switch statement. I'm guessing that is why I didn't get errors before.



               
               

               
            

Legacy_Proleric

  • Hero Member
  • *****
  • Posts: 1750
  • Karma: +0/-0
Declaring variables inside a switch/case statement
« Reply #8 on: August 28, 2015, 08:58:56 am »


               

Well, this is very odd. The following is accepted by the Bioware compiler, and gives the feedback i=1 in game. What am I missing?


 


void main()
{
int n = 2;
switch(n)
  {
    case 1: break;
    //
    case 2:
            int i = 1;
            SendMessageToPC(GetFirstPC(), "i=" + IntToString(i));
            break;
  }
}


               
               

               
            

Legacy_meaglyn

  • Hero Member
  • *****
  • Posts: 1451
  • Karma: +0/-0
Declaring variables inside a switch/case statement
« Reply #9 on: August 29, 2015, 01:27:39 am »


               

It seems the in game compiler allows it if it is the last option (case or default) in the switch statement.  That sort of makes sense since when you get the error it's on the following case statement and says "Skipping Declaration due to case statement disallowed". I haven't looked at code it generated.