StandAloneInterpreter - rmu75/linuxcnc-wiki GitHub Wiki
date: '2005-06-16T12:48:19' title: StandAloneInterpreter
By Matthew Shaver
crappy formatting by fenn :) So, I started out to compile a stand alone interpreter (which I had never done before). I found Tom Kramer"s page with a sample Makefile:
<http://www.isd.mel.nist.gov/personnel/kramer/pubs/RS274NGC_3.web/RS274NGC_35a.html>
I put the Makefile (see below for a copy) in the rs274ngc_new directory
of EMC1 and ran it with "make rs274" and it compiled (it runs by typing
"./rs274" from the directory containing that file). Go ahead, try it
yourself!
Next I wanted to make a standalone interpreter in EMC2, but the source
files are so different that the old Makefile won"t work. Also, after
asking a few questions on the developer"s list, I found out that the
EMC2 version of the interpreter is encapsulated in a C++ class. All
these changes are good things IMHO, but I couldn"t build a standalone
interpreter...
The interpreter is really just a library of related functions that work
together to read RS274 files and output canonical commands, it doesn"t
have a main() function which would allow it to be linked as an
executable program by itself, nor does it have a "user interface". This
library of interpreter functions is baled up into an archive file called
rs274.o by a rule in the Makefile in the rs274ncg directory in EMC2. The
old standalone interpreters in EMC1 were made by linking the library of
rs274ngc interpreter functions with code in a file named driver.cc which
provides main() and the user interface, and also with
stand_alone_canon.cc (for rs274ngc) or canon_pre.cc (for rs274ngc_new)
which provides an implementation of the canonical functions that just
print out the canonical functions as a text string when they"re called.
These canon.cc files are only used for the standalone interpreter,
emccanon.cc is the file that defines the same functions that respond to
the canonical commands in a real, working EMC (1 or 2).
Well, I got a copy of driver.cc and canon_pre.cc (I renamed this to
saicanon.cc as I thought it made more sense), put "em all in the task
directory in EMC2, and I commenced to hackin" on these files, and on
task/Makefile.
I finally got a standalone interpreter to work in EMC2.
It would run, and the user interface would come up, but it wouldn"t
actually interpret any code because there was a problem with reading
the .ini file.
One thing I did learn from this exercise is that the order of the files
specified on the linker command line is _important_. The files need to
be listed in "function call order", that is your top level file with
main() in it goes first, then files that contain functions called from
the top level, then files with functions called from the previous files,
and so on...
I thought about fixing the problem with .ini file reading, but then
another thing happened...
Along about this time I noticed that there was a directory called
"canterp" which, it turns out, contains a canonical interpreter. That
is, it"s an interpreter that reads in the printed canonical output of a
standalone interpreter, and outputs the canonical commands (this means
it calls the canonical functions declared in canon.hh).
Of course I immediately decided I _had_ to get that working as well.
And a standalone version too!
The "rs274" standalone interpreter is made by linking driver.o, rs274.o,
and saicanon.o. To experience true happiness, I need to be able to
create a standalone "canterp" by linking the _same_ driver.o and
saicanon.o with canterp.o.
This means that the function names, at least the function names that are
called by code in other files (called "public" functions), need to be
the same in both (and any future) interpreters. Also, the interpreter
functions return integer codes to their callers to indicate either
success, or many, many different kinds of failure (see
nml_intf/interp_return.hh and rs274ngc/rs274ngc_return.hh for the list).
To make things more human readable, these integer return values are
given descriptive names with a series of #define statements in those .hh
header files. As an example, the success code is zero, which was
#defined as RS274NGC_OK.
Therein were some problems...
1. Since code external to the interpreter sometimes tests for specific
return values, and it references them by their #defined name, it seems
confusing if a NON-rs274 interpreter had to return a value of
"RS274NGC_OK"!
2. Some of the error codes are very specific to problems associated with
"G" and "M" codes, and may not be applicable to other types of
interpreters.
Luckily, the only external tests for specific return code values were
for:
#define RS274NGC_OK 0
#define RS274NGC_EXIT 1
#define RS274NGC_EXECUTE_FINISH 2
#define RS274NGC_ENDFILE 3
#define RS274NGC_FILE_NOT_OPEN 4
#define RS274NGC_MIN_ERROR 3
(MIN_ERROR is a value that indicates the index of the last "successful"
return code, it"s not a return code itself)
and these were replaced by:
#define INTERP_OK 0
#define INTERP_EXIT 1
#define INTERP_EXECUTE_FINISH 2
#define INTERP_ENDFILE 3
#define INTERP_FILE_NOT_OPEN 4
#define INTERP_MIN_ERROR 3
and put in a new header file called nml_intf/interp_return.hh. Also,
rs274ngc_return.hh was removed from the HEADERS section of
rs274ngc/Makefile which means that this header file is effectively
"private" and external source files (those outside the src/emc/rs274ngc
directory) shouldn"t (and can"t easily) #include it.
So, now we have "public" and "private" return codes, and the "public"
ones are given generic names...
Well now, the next problem is to determine what "public" functions are
needed, generalize their names so that any interpreter can implement
them, and separate their declarations out into a "public" header file
(nml_intf/interp.hh).
Not too long ago, all the functions in the rs274ngc intepreter were
named rs274ngc_functionxxx(), but Paul Corner was kind enough to drop
the "rs274ngc_" prefix from all the sources, which was a great first
step towards generalizing the interpreter function call interface.
Here"s how to find out what interpreter functions are being called:
1. The rs274ngc interpreter in EMC2 (and presumably any future
interpreter) encapsulates its functions into a C++ class named Interp.
2. Software systems that use the interpreter have to start by creating
an instance of the interpreter class, which is done in emctask.. &
driver.cc with the expression:
Interp interp;
3. This means that "interp" is the name of the interpreter object, and
its functions are called like this:
interp.function_name();
So...
4. You can cd to the top of the emc2 directory structure and type:
grep -R "interp\." *
This will list on the screen every line of every file in every directory
that contains the string "interp." (the "\" in the command is called an
"escape" and is needed to cause grep to treat the "." as a literal
character and not as some special bit of command punctuation).
5. Now, we need to sift through all this info, sort out the duplicates,
and make a list. Only two files contain calls to these functions, here
they are:
Function emctask.cc driver.cc
_________________________________________
init() X X
ini_load() X
synch() X
exit() X
file_name() X
open() X X
read() X X
execute() X X
close() X X
error_text() X X
line_text() X
stack_name() X X
line() X
line_length() X-See Note
sequence_number() X-See Note
command() X
file() X
active_g_codes() X X-See Note
active_m_codes() X X-See Note
active_settings() X X-See Note
Note: /* called to exercise the function */ (results aren"t used)
Not a huge list, nothing really compared to the total number of
functions in all the rs274ngc source code. Perhaps upon further
examination of what these functions do, and how their results are used,
the number of required public functions could be reduced further. For
starters, line_length() and sequence_number() are only called by
driver.cc, and the results aren"t used! They can probably go... In fact
if they"re not used inside the rs274ngc source code, then they can be
removed entirely!
Most of these names are fairly generic, which is good, except for
active_g_codes() and active_m_codes(). If the EMC is running an
interpreter that doesn"t use G and M codes, then what should these
functions do?
Let"s see what the results of these functions are used for in emctask.cc
(in driver.cc the results aren"t used). The functions active_g_codes(),
active_m_codes(), and active_settings()are called only once in this
function:
int emcTaskUpdate(EMC_TASK_STAT * stat)
{
<...>
// update active G and M codes
interp.active_g_codes(&stat->activeGCodes[0]);
interp.active_m_codes(&stat->activeMCodes[0]);
interp.active_settings(&stat->activeSettings[0]);
<...>
}
Ok, so the data from these functions is stored in some array members of
a struct. I wonder if there are any other references to "activeGCodes",
etc. in any other file? Once again, grep to the rescue!
[emc2]$ grep -R "activeGCodes" *
src/emc/usr_intf/emcsh.cc: code = emcStatus->task.activeGCodes[t];
src/emc/task/emctask.cc: interp.active_g_codes(&stat->activeGCodes
[0]);
src/emc/nml_intf/emc.hh: int activeGCodes[ACTIVE_G_CODES];
src/emc/nml_intf/emc.cc: cms->update(activeGCodes, 12);
src/emc/nml_intf/emcops.cc: activeGCodes[t] = -1;
Well, the only place where this data is read out of the array is in
emcsh.cc:
static int emc_program_codes(ClientData clientdata,
Tcl_Interp * interp, int objc, Tcl_Obj * CONST objv[])
{
char codes_string[256];
char string[256];
int t;
int code;
if (objc != 1) {
Tcl_SetResult(interp, "emc_program_codes: need no args",
TCL_VOLATILE);
return TCL_ERROR;
}
if (emcUpdateType == EMC_UPDATE_AUTO) {
updateStatus();
}
// fill in the active G codes
codes_string[0] = 0;
for (t = 1; t < ACTIVE_G_CODES; t++) {
code = emcStatus->task.activeGCodes[t];
if (code == -1) {
continue;
}
if (code % 10) {
sprintf(string, "G%.1f ", (double) code / 10.0);
} else {
sprintf(string, "G%d ", code / 10);
}
strcat(codes_string, string);
}
// fill in the active M codes, settings too
for (t = 1; t < ACTIVE_M_CODES; t++) {
code = emcStatus->task.activeMCodes[t];
if (code == -1) {
continue;
}
sprintf(string, "M%d ", code);
strcat(codes_string, string);
}
// fill in F and S codes also
sprintf(string, "F%.0f ", emcStatus->task.activeSettings[1]);
strcat(codes_string, string);
sprintf(string, "S%.0f", fabs(emcStatus->task.activeSettings[2]));
strcat(codes_string, string);
Tcl_SetResult(interp, codes_string, TCL_VOLATILE);
return TCL_OK;
}
So, when emc_program_codes() is called, the values from these arrays of
integers are read out, and printed to strings which are concatenated
into one big string named codes_string. (You may have noticed the the G
code values are divided by ten. For an explanation of this, see the file
rs274ngc/interp_write.cc where write_g_codes() is defined.)
Who calls this function to get this string?
Glad you asked!
[emc2]$ grep -R "emc_program_codes" *
src/emc/usr_intf/emcsh.cc: emc_program_codes
src/emc/usr_intf/emcsh.cc:static int emc_program_codes(ClientData
clientdata,
src/emc/usr_intf/emcsh.cc: Tcl_SetResult(interp,
"emc_program_codes: need no args",
src/emc/usr_intf/emcsh.cc: Tcl_CreateObjCommand(interp,
"emc_program_codes", emc_program_codes,
tcl/tkemc.tcl: set programcodestring [emc_program_codes]
tcl/mini.tcl: set programcodestring [emc_program_codes]
Yep! It"s called from the GUI! That"s where they get that list of active
G, M, F, and S codes from!
And I think they"re going to be wrong...
...because the machine operator wants to see what codes are active
_right now_, but the interpreter can be many lines ahead of actual
execution. Plus, it"s not a good idea to hard code this message format
if we plan on supporting non-G & M code type interpreters.
One thought I had was to make a new canonical function (perhaps
DISPLAY_ACTIVE_CODES() ) that the interpreter could use to indicate
changes in the active program codes. These canonical commands would be
put on the interp list and if the pre-conditions are specified right,
the message should be displayed to the machine operator at the correct
time. Also, it could be an arbitrary string (or other data type, even a
picture...) that would be appropriate for whatever interpreter is in
use.
Another possibility is to get rid of this whole mess entirely and to
display program status to the machine operator based on the _canonical_
function status. After all, below the interpreter, that"s all we"ve
really got. The EMC doesn"t run on G code, it runs on canonical
canonical commands.
Anyway, I thought some of you on the list would enjoy a sort of "stream
of consciousness" description of my thought processes as I dig through
the code, and I will welcome comments.
Thanks,
Matt
P.S.:
Copy of Makefile for SAI in EMC1:
COMPILE = g++ -c -v -g
LINK = g++ -v
canon.o: canon_pre.cc canon.hh
$(COMPILE) -o canon.o canon_pre.cc
canon_abc.o: canon_pre.cc canon.hh
$(COMPILE) -DAA -DBB -DCC -o canon_abc.o canon_pre.cc
canon_ac.o: canon_pre.cc canon.hh
$(COMPILE) -DAA -DCC -o canon_ac.o canon_pre.cc
driver.o: driver.cc canon.hh rs274ngc.hh rs274ngc_return.hh
$(COMPILE) -o driver.o driver.cc
rs274: rs274.o canon.o driver.o
$(LINK) -o rs274 rs274.o canon.o driver.o -lm
rs274.o: rs274ngc_pre.cc canon.hh rs274ngc.hh rs274ngc_errors.cc
rs274ngc_return.hh
$(COMPILE) -o rs274.o rs274ngc_pre.cc
rs274ac: rs274ac.o canon_ac.o driver.o
$(LINK) -o rs274ac rs274ac.o canon_ac.o driver.o -lm
rs274ac.o: rs274ngc_pre.cc canon.hh rs274ngc.hh rs274ngc_errors.cc
rs274ngc_return.hh
$(COMPILE) -DAA -DCC -o rs274ac.o rs274ngc_pre.cc
rs274_all.o: rs274ngc_pre.cc canon.hh rs274ngc.hh rs274ngc_errors.cc
rs274ngc_return.hh
$(COMPILE) -DALL_AXES -o rs274_all.o rs274ngc_pre.cc
rs274_all: rs274_all.o canon_abc.o driver.o
$(LINK) -o rs274_all rs274_all.o