lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]

When I first started hacking Lua I really wanted to implement a C style switch/case statement, back then I had no idea how to even go about such an endeavour! Well, I am now very happy to say that I have successfully implemented the switch/case statement for Lua, and have attached the patch to this post.

This patch does NOT require any special variant of Lua. Code compiled with this patch WILL run on any non-patched Lua virtual machine as it only uses the standard flow control opcodes already present in Lua (OP_NE and OP_JMP).

The bytecode generated by this patch is the same size as an equivalent if/elseif/else block (same number of if/elseif as cases in switch block). The switch statement creates a temp local variable (as the for loops do) to hold the test condition for the case tests. This is very useful when testing a global variable as the global is only loaded once at the start of the switch, unlike if/elseif which potentially would test the global at each step.

Actually, if every case statement has a break then the resulting bytecode will be 1 byte larger than the same if/elseif statements, but only if both are using a local. If a global is involved then the switch bytecode will always be smaller than the if/elseif bytecode.

Syntax of the switch/case statement:

switch <variable>      -- variable must be global or local
  case <constant>      -- constant: nil, true, false, number or string
    <block>            -- standard Lua block, all statements valid
    [break]            -- optional break, if not present will fall through
  case <constant>      -- to the block of the next case statement
    [break]            -- repeat as many cases as you need
  case <constant>      -- specifying a constant more than once will
    <block>            -- generate a syntax error at compile time
  else                 -- optional else block if no case conditions matched
    <block>            -- if last statement in else is a break it will be
end                    -- optimised out and will not appear in the bytecode

Due to the limitations of how this needed to be implemented it is wise to put the most frequent case conditions at the top of the switch statement, as each case needs to be checked in turn until one matches. This is achieved by using OP_NE at the first case, if false then an OP_JMP is done to the next case, and if no matches then to the 'else' block or out of the switch block.

Once a case does match then the block is executed, at the end of each block is an OP_JMP command that increases PC by two positions, which skips the next OP_NE and OP_JMP, effectively skipping the case statements and allowing fall through to work. If a 'break' statement is the last thing in a case block then the OP_JMP over the next case is replaced with a standard break jump to the end of the switch block.

For my Lunia engine I plan to implement a new VM opcode; OP_JMPTBL. This will essentially use the constant number as an offset into a jump table to simply jump to the correct case block, negating the need to scan down the case statements to find the right one. This can not be done with the standard Lua distribution, however, the patch attached to this post does work with vanilla Lua! :)

If anyone is interested please try the patch and let me know how it works for you. Of course, if anyone finds any bugs or obvious things I have missed I would love to hear about them. I have tested things pretty well, but you never know, maybe I forgot about negative numbers again or something! ;)


Attachment: switchcase.patch
Description: Binary data