lua-users home
lua-l archive

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


It was thus said that the Great Paige DePol once stated:
> 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.

  With "break", it's not quite the same as an if/elseif/else block.  For
instance (first example):

	switch(x)
	  case 1
	    print("foo")
	    break
	  case 2
	    print("bar")
	    break
	  else
	    print("wtf")
	end

*is* the same as:

	if x == 1 then
	  print("foo")
	elseif x == 2 then
	  print("bar")
	else
	  print("wtf")
	end

But this:

	switch(x)
	  case 1
	    print("foo")
	  case 2
	    print("bar")
	  else
	    print("wtf")
	end

is more like:

	if x == 1 then
	  print("foo")
	end
	if x == 1 or x == 2 then
	  print("bar")
	end
	-- ... um ... is "wtf" always printed?

> 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
>     <block>
>     [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
>     [break]
>   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.

  Okay, if each case has a "break" statement [1] then an optimization might
be to convert the switch statement to a table.  Go, going back to my first
example, it would internally be converted to:

	_switch0000 = {
		[1] = function() print("foo") end,
		[2] = function() print("bar") end,
	}

	if _switch0000[x] == nil then
	  print("wtf")
	else
	  _switch0000[x]()
	end

  Actually, now that I think about it, the breakless blocks can be
implemented as:

	_switch0001 = {
		[1] = function() print("foo") _switch0001[2]() end,
		[2] = function() print("bar") _switch0001._default() end,
	        _default = function() print("wtf") end,
	end

	if _switch0001[x] == nil then
	  _switch0001._default()
	else
	  _switch0001[x]()
	end

> 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! :)

  Mind the gap!

	switch(x)
	  case 10 ... break
	  case 20 ... break
	  case 100 ... break
	end

  Or even ... Mind the type!

	switch(x)
	  case true ... break
	  case nil  ... break
	  case 10   ... break
	  case "foobar" ... break
	  case print ... break
	end

  -spc (I've found that I don't really miss "switch" as much as I thought I
	would ... )

[1]	That is, if you keep that syntax.  The number of times I've *not*
	used "break" in C code I can cound on the fingers of one hand and
	still have a few fingers left over.  It might be better to have a
	"fallthrough" statement.