[Home]StandAloneInterpreter

LinuxCNCKnowledgeBase | RecentChanges | PageIndex | Preferences | LinuxCNC.org

No diff available--this is the first major revision. (no other diffs)
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


LinuxCNCKnowledgeBase | RecentChanges | PageIndex | Preferences | LinuxCNC.org
This page is read-only. Follow the BasicSteps to edit pages. | View other revisions
Last edited June 16, 2005 10:48 am by Fenn (diff)
Search:
Published under a Creative Commons License