[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Switch/Case with Fallthrough
- From: Sean Conner <sean@...>
- Date: Wed, 20 Nov 2013 13:20:44 -0500
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.