lua-users home
lua-l archive

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


Further to my last post, I had a little think, and you can simulate the
case implementation method I was suggesting (but without the convenient
language extension) by changing the "function" fallback as follows:

    If the object being called is a table, use the first argument to
    index it, Otherwise call the old fallback.

    If the resulting object is a function, execute it with no
    arguments and throw away any results you get.

    If the index operation fails, try the index "default", and
    proceed as above if you get a hit.

This is probably best done in 'C':

-----------------------------------------------------------------------------
void NewFunFB(void)
{
    lua_Object tbl = lua_getparam(1);
    lua_Object idx = lua_getparam(2);
    lua_Object fun;

    if (!lua_istable(tbl) || !lua_isstring(idx))  /* isstring() works for  */
        lua_call("OldFunFB");                     /* numbers too, remember */
    else
    {
        lua_pushobject(tbl);
        lua_pushobject(idx);
        fun = lua_getsubscript();
        if (lua_isfunction(fun))
            lua_callfunction(fun);
        else
        {
            lua_pushobject(tbl);
            lua_pushstring("default"); /* or whatever you want to call it */
            fun = lua_getsubscript();
            if (lua_isfunction(fun))
                lua_callfunction(fun);
            else
                lua_error("function table has no \"default\" member");
        }
    }
}
-----------------------------------------------------------------------------

Then in main() you do:

-----------------------------------------------------------------------------
lua_register("NewFunFB",NewFunFB);
lua_dostring("OldFunFB = setfallback(\"function\",NewFunFB)");
-----------------------------------------------------------------------------

You might want to do the chaining using a lua_ref, rather than with a global
name as I have here; it would be neater. You also might like to make the
default get called when lua_isnil(idx) is true, it's a debatable point.

Having done this you can write some Lua like this:

Case = {}                              -- the table that will get called

function Case:foo()     y = "abc" end  -- this is the obvious way to do it
function Case["bar"]()  y = 1     end  -- or you can do it like this
function Case[1]()      y = "xyz" end  -- wow, this works too!
Case[42] = Case[1]                     -- multiple values that call the
                                       -- same code are easy

function Case:default() y = 99    end  -- this name is just a convention,
                                       -- see the C code above.

x = "foo"  Case(x)  print(x,y)
foo
abc
x = "bar"  Case(x)  print(x,y)
bar
1
x = 1      Case(x)  print(x,y)
1
xyz
x = 42     Case(x)  print(x,y)
42
xyz
x = "baz"  Case(x)  print(x,y)
baz
99
x = 1      Case(x)  print(x,y)
1
99

Obviously, this is cracking nuts with sledgehammers, but if there were lots
more cases to cover a breakpoint would be reached beyond which it was worth
the effort. I'm betting you'ld see it round about the 10 cases mark...

Notice that in the body of the Case:foo() function the argument "self" will
be 'nil' unless you make your fallback even more sophisticated. Hmmm... can
I think of a use for that too? :-)

                        ------ *** -------

Looking at how you might go about building such a table from inside the
parser, You *would* want to allow multiple values to call the same code
(as shown in the above; it's so easy it seems a shame not to). For an
example, here's Yet Another Suggested Syntax:

case <expr> when
  <const>{,<const>} do
    <block> end
  ...
  default
    <block> end
end

To parse this you would have to either:

(a) Scan the list of constants into a temporary table, scan the associated
    code into an anonymous function body, then patch references to it into
    your jump table at each the indices held in your temporary table.

(b) Use tail recursion to scan in a constant followed by either a comma (in
    which case you recurse, then patch the returned function reference into
    the constant index) or the start of the code body, in which case you scan
    it into your anonymous function, patch the index and return a reference
    to the body.

Both sound a bit inefficient to me, so why not reverse the traditional
syntax ordering in the case branches to allow a L->R scan? here's YASS:

case <expr>
  do <block> when <const>{,<const>}
  ...
  do <block> otherwise
end

Eg:     case a+b
          do  x=1  print("yes")    when 0
          do  x=0  print("no")     when 1,2,3,4,5,6
          do  x=99 print("what?")  otherwise
        end

Is this too radical, or what? I suppose it's harder for humans to read
through the selector list when the code blocks get large, but not *much*
harder if you're careful with layout...

--  Mark Ian Barlow                Non-Linear Control Consultants Ltd.
    -----------------------------------------------------------------
    Mark@nlcc.demon.co.uk            Voice / Fax: +44 (0)1207 562 154