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