Part 2: Embed Lua in script (60%)

Overview

In this part, we will embed Lua in script, by modifying the application’s source code. Our CS 131 version of script is called lscript, and is in lscript.c.

Even though lscript.c is a short C program, we do not expect you to understand the totality of how lscript works (because we will be making a few targeted changes and mostly deleting code). Nevertheless, we can observe a few things about the general nature of code. In particular, it has almost no comments, and makes liberal use of global variables. Your additions should have comments if/when appropriate, but you may follow the pattern of the code and use global variables freely. The code is written in C89, which has a few strange limitations (e.g., all local variables must be declared at the start of the function). Your additions may use C99 features (for example, you can intermingle local variable declarations with other code), but you may not switch the code over to C++.

To embed Lua and make lscript extensible, our general plan is to make Lua available to the application, then move functionality from C to Lua. We will proceed step-by-step.

Step 0: Review the plan

Your task: Read over this short description of our design. It describes how lscript currently works and how we will modify it to embed Lua.

There is nothing to write or turn in for this part, but you will want to understand it and refer back to it as you work.

Step 1: Add the baseline code needed to embed Lua

Your task: Modify lscript.c to embed Lua.

Using the example program from the previous part of the assignment as a guide, modify lscript.c so that it:

  1. Uses includes to make Lua’s API available. (Add the Lua includes after the includes already in the file).

  2. Declares a global variable of type lua_State *, for the Lua-interpreter state. (Note: the program in the previous part of the assignment used a local variable, but we will use a global one, to follow the conventions of the lscript code.)

  3. At the start of main (after the local variable declarations, if you like):
    • Initializes the Lua-interpreter state with a call to luaL_newstate.
    • Starts the Lua interpreter (by loading the standard Lua libraries with luaL_openlibs).
    • Loads and executes lscript.lua with luaL_dofile. Note: You will probably want to port over the LUA_CHECK macro from the example code.
  4. At the end of main, shuts down the Lua interpreter by calling lua_close.

When this part is complete, you should be able to rebuild the lscript program using make lscript. When you run ./lscript, it should print “Lua code loaded!” to standard error when the program starts. (Note: the C program is not yet calling any of the Lua functions in lscript.lua.)

Step 2: Make C functions callable from Lua

Your task: In lscript.c, wrap and register the C functions shown in the table below so that they are available to be called from Lua.

Lua Function C Function
writeToSubprocess writetosubprocess
writeToUser writetouser
setPeriod setperiod
exit done
terminalIsEchoing isechoing

For example, we want lscript.lua to be able to pass a single Lua string to writeToSubprocess, which will call our C wrapper function, translate the arguments as needed, and call the existing C function writetosubprocess. Don’t forget that when wrapping writetosubprocess and writetouser we want to use luaL_checklstring, not lua_tostring.

IMPORTANT: Be sure to register the functions after Lua is initialized, but before the C code loads lscript.lua.

When implementing, you can take inspiration from the functions that you wrapped in the previous part of the assignment.

When this part is complete, the code should build, and we should notice no changes in the lscript program behavior.

Step 3: Interpose Lua between the C code and the subprocess

Your task: In this part, we will redirect much of the important script functionality (e.g., reading and writing from the subprocess) through Lua.

  • Modify lscript.c so that instead of calling writetosubprocess and writetouser, it instead calls the Lua functions userInputEvent and subprocessOutputEvent, respectively. (When passing a string to these functions, use lua_pushlstring; in both cases the length of the string is in the cc variable).
  • Change the C function periodic so that it also calls the Lua function periodic (in addition to flushing the log).

When this part is complete, the code should build and we should notice no changes in the lscript program behavior.

Check that it works and experiment with the interposition

So far, we have only added a level of indirection: The C code calls to Lua, which calls back to C. Therefore, the program should behave exactly as it did before we started modifying it.

However, because we have embedded Lua in the program and interposed on the important function calls, we can now start to change the behavior without having to recompile the C code!

Your task: Experiment with lscript.lua by changing the function periodic to pass a string of your choice to writeToUser. Invoke ./lscript -t 3 to make sure that your added action happens every three seconds.

Notice that you can change the message by editing lscript.lua and the lscript program behavior will change, without having to recompile it!

Delete or comment out your changes when you are done.

Step 4: Move logging functionality from C to Lua

Your task: Move the logging functionality from C to Lua.

First, we want to remove logging from C:

  • In lscript.c, delete all code related to opening, writing to, and closing the log file, including informational messages. An easy way to do so is to delete the declaration for the variable fscript and then search for lines that (transitively) refer to that variable and remove them. Make sure the program still compiles and “works” (except for writing to a log file, because we just deleted that code!).

Next, we want to add logging to Lua:

  • Pass the logfile name to Lua. The lscript program takes an argument that is the filename for the log. In the main function in lscript.c, store the program’s filename argument into a Lua global variable called logname. (The strlen function will compute the length of a string.)

  • Make sure Lua can start and stop logging. In lscript.c, after logname is set, call the Lua function init, which will start logging. Also, at the beginning of the C function done, call the Lua function terminate, which will stop logging.

Test the lscript program. Once again, the program should behave the same, but with Lua managing the task of writing to the log.

Step 5: Let Lua handle arguments to lscript

Your task: Move the remaining argument-handling code from C to Lua.

The Lua program can perform the argument-handling, but we must first pass the arguments from C to Lua, then remove the argument-handling code from the C program. This part will perhaps require the most careful changes in lscript.c. Here is how to proceed:

  • Delete the option-flag variables aflg and kflg from lscript.c.
  • Replace the switch that sets the variables with code that puts all the passed options into the options table (a global variable already created by the Lua code).

For option names, note that Lua does not really let us push a character onto the stack. Instead, we want to create a string from a single character. If we have a character whose value is stored in the variable ch, we can create a C-style string (which is terminated with the NUL character) like so:

  char argname[] = {ch, '\0'};

When an option has a value (such as a string), we want to store that value in the table entry corresponding to the option; otherwise we will store the boolean value true as the value for that option. Thus, we want

    ./lscript -q -t 5

to cause the equivalent of

    options.q = true;
    options.t = "5";

It may help to know that for options that have values, C’s getopt function points the global variable optarg to a string that contains that value; otherwise optarg is NULL.

If the code compiles and behaves as before (including with various options passed), then we are now ready for the payoff: extending lscript in Lua!