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:
-
Uses
include
s to make Lua’s API available. (Add the Luainclude
s after theinclude
s already in the file). -
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 thelscript
code.) - 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
withluaL_dofile
. Note: You will probably want to port over theLUA_CHECK
macro from the example code.
- Initializes the Lua-interpreter state with a call to
- At the end of
main
, shuts down the Lua interpreter by callinglua_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 callingwritetosubprocess
andwritetouser
, it instead calls the Lua functionsuserInputEvent
andsubprocessOutputEvent
, respectively. (When passing a string to these functions, uselua_pushlstring
; in both cases the length of the string is in thecc
variable).
- Change the C function
periodic
so that it also calls the Lua functionperiodic
(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 variablefscript
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 themain
function inlscript.c
, store the program’s filename argument into a Lua global variable calledlogname
. (Thestrlen
function will compute the length of a string.) -
Make sure Lua can start and stop logging. In
lscript.c
, afterlogname
is set, call the Lua functioninit
, which will start logging. Also, at the beginning of the C functiondone
, call the Lua functionterminate
, 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
andkflg
fromlscript.c
. - Replace the
switch
that sets the variables with code that puts all the passed options into theoptions
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!