Change the Color of a Light Using Lua
This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.
The fourth edition targets Lua 5.3 and is available at Amazon and other bookstores.
By buying the book, you also help to support the Lua project.
25.1 – Table Manipulation
Let us adopt that attitude: Now, we want to configure a background color for the window, too. We will assume that the final color specification is composed of three numbers, where each number is a color component in RGB. Usually, in C, those numbers are integers in some range like [0,255]. In Lua, because all numbers are real, we can use the more natural range [0,1].
A naive approach here is to ask the user to set each component in a different global variable:
-- configuration file for program `pp' width = 200 height = 300 background_red = 0.30 background_green = 0.10 background_blue = 0This approach has two drawbacks: It is too verbose (real programs may need dozens of different colors, for window background, window foreground, menu background, etc.); and there is no way to predefine common colors, so that, later, the user can simply write something like
background = WHITE
. To avoid these drawbacks, we will use a table to represent a color: background = {r=0.30, g=0.10, b=0}The use of tables gives more structure to the script; now it is easy for the user (or for the application) to predefine colors for later use in the configuration file:
BLUE = {r=0, g=0, b=1} ... background = BLUETo get these values in C, we can do as follows:
lua_getglobal(L, "background"); if (!lua_istable(L, -1)) error(L, "`background' is not a valid color table"); red = getfield("r"); green = getfield("g"); blue = getfield("b");As usual, we first get the value of the global variable
background
and ensure that it is a table. Next, we use getfield
to get each color component. This function is not part of the API; we must define it, as follows: #define MAX_COLOR 255 /* assume that table is on the stack top */ int getfield (const char *key) { int result; lua_pushstring(L, key); lua_gettable(L, -2); /* get background[key] */ if (!lua_isnumber(L, -1)) error(L, "invalid component in background color"); result = (int)lua_tonumber(L, -1) * MAX_COLOR; lua_pop(L, 1); /* remove number */ return result; }Again, we face the problem of polymorphism: There are potentially many versions of
getfield
functions, varying the key type, value type, error handling, etc. The Lua API offers a single function, lua_gettable
. It receives the position of the table in the stack, pops the key from the stack, and pushes the corresponding value. Our private getfield
assumes that the table is on the top of the stack; so, after pushing the key (lua_pushstring
), the table will be at index -2. Before returning, getfield
pops the retrieved value from the stack, to leave the stack at the same level that it was before the call. We will extend our example a little further and introduce color names for the user. The user can still use color tables, but she can also use predefined names for the more common colors. To implement this feature, we need a color table in our C application:
struct ColorTable { char *name; unsigned char red, green, blue; } colortable[] = { {"WHITE", MAX_COLOR, MAX_COLOR, MAX_COLOR}, {"RED", MAX_COLOR, 0, 0}, {"GREEN", 0, MAX_COLOR, 0}, {"BLUE", 0, 0, MAX_COLOR}, {"BLACK", 0, 0, 0}, ... {NULL, 0, 0, 0} /* sentinel */ };
Our implementation will create global variables with the color names and initialize these variables using color tables. The result is the same as if the user had the following lines in her script:
WHITE = {r=1, g=1, b=1} RED = {r=1, g=0, b=0} ...The only difference from these user-defined colors is that the application defines these colors in C, before running the user script.
To set the table fields, we define an auxiliary function, setfield
; it pushes the index and the field value on the stack, and then calls lua_settable
:
/* assume that table is at the top */ void setfield (const char *index, int value) { lua_pushstring(L, index); lua_pushnumber(L, (double)value/MAX_COLOR); lua_settable(L, -3); }Like other API functions,
lua_settable
works for many different types, so it gets all its operands from the stack. It receives the table index as an argument and pops the key and the value. The setfield
function assumes that before the call the table is at the top of the stack (index -1); after pushing the index and the value, the table will be at index -3. The setcolor
function defines a single color. It must create a table, set the appropriate fields, and assign this table to the corresponding global variable:
void setcolor (struct ColorTable *ct) { lua_newtable(L); /* creates a table */ setfield("r", ct->red); /* table.r = ct->r */ setfield("g", ct->green); /* table.g = ct->g */ setfield("b", ct->blue); /* table.b = ct->b */ lua_setglobal(L, ct->name); /* `name' = table */ }The
lua_newtable
function creates an empty table and pushes it on the stack; the setfield
calls set the table fields; finally, lua_setglobal
pops the table and sets it as the value of the global with the given name. With those previous functions, the following loop will register all colors in the application's global environment:
int i = 0; while (colortable[i].name != NULL) setcolor(&colortable[i++]);Remember that the application must execute this loop before running the user script.
There is another option for implementing named colors. Instead of global variables, the user can denote color names with strings, writing her settings as background = "BLUE"
. Therefore, background
can be either a table or a string. With this implementation, the application does not need to do anything before running the user's script. Instead, it needs more work to get a color. When it gets the value of the variable background
, it has to test whether the value has type string, and then look up the string in the color table:
lua_getglobal(L, "background"); if (lua_isstring(L, -1)) { const char *name = lua_tostring(L, -1); int i = 0; while (colortable[i].name != NULL && strcmp(colorname, colortable[i].name) != 0) i++; if (colortable[i].name == NULL) /* string not found? */ error(L, "invalid color name (%s)", colorname); else { /* use colortable[i] */ red = colortable[i].red; green = colortable[i].green; blue = colortable[i].blue; } } else if (lua_istable(L, -1)) { red = getfield("r"); green = getfield("g"); blue = getfield("b"); } else error(L, "invalid value for `background'");
What is the best option? In C programs, the use of strings to denote options is not a good practice, because the compiler cannot detect misspellings. In Lua, however, global variables do not need declarations, so Lua does not signal any error when a user misspells a color name. If the user writes WITE
instead of WHITE
, the background
variable receives nil (the value of WITE
, a variable not initialized), and that is all that the application knows: that background
is nil. There is no other information about what is wrong. With strings, on the other hand, the value of background
would be the misspelled string; so, the application can add that information to the error message. The application can also compare strings regardless of case, so that a user can write "white"
, "WHITE"
, or even "White"
. Moreover, if the user script is small and there are many colors, it may be odd to register hundreds of colors (and to create hundreds of tables and global variables) only for the user to choose a few. With strings, you avoid this overhead.
Copyright © 2003–2004 Roberto Ierusalimschy. All rights reserved. |
Change the Color of a Light Using Lua
Source: https://www.lua.org/pil/25.1.html