Ron,
Your simple question really requires a very long narrative to fully explain. I’m going to try to keep to the short version…
MSM uses both #expand and RunScript. This of course begs the question: Why one vs. the other? The short answer is they each have different limitations… and “history”.
The Mach script language is a Basic dialect (nothing can be done about that now), and you inherently get all the pros and cons that come with programming in Basic. A basic script naturally wants to be a totally self contained widget. That doesn’t match very well with a project that requires you place code into 100s of buttons….
As the project progressed, I really started to dislike parts of the code and I began to really seriously worry about maintainability and support issues.
What I really needed to be able to do in a cypress script was to define an arbitrary function or subroutine, have just one source copy of it, and be able to just use it from any script, with all the normal parameter passing mechanisms that Basic languages have.
I researched the Cypress engine interfaces and realized that this should be pretty easy to do. The cypress engine includes all the hooks needed to do this. Cypress anticipated this need and it’s part of the Cypress product. Naively, I contacted Brian to see if I could cajole him into “turning this on”…. Long story short - due to the way the Cypress Engine was spliced into Mach way back in the mists of time, this turned out to be essentially impossible (or at least way more work that it was reasonable to do inside Mach3 V3).
So we turned to discussing what could be done with more reasonable amounts of effort, within a useful time frame.
The first mechanism I tried was RunScript. It already existed but not been exposed as an interface.
1) Code() and MCode files -> RunScript()
Prior to RunScript, the only container available to hold code that you want to call from multiple places was a M123.m1s file – and the only way to “Call or run” it was with the Code() API. This was designed to allow simple user defined Mcodes. It was not intended as a general “subroutine call mechanism”.
There are some major problems with using Code() as a script subroutine mechanism:
a) The Mach Code() interface uses the GCode engine to “run” the M123 file (after all a M123 file is really a Mcode). When you think about this, you come to realize that the name of the Code() interface is a bit misleading, it would be more accurate to name it GCode() or even something like InsertGCodeIntoQueue().
b) The need to invoke the gcode engine to run script code that does not really have anything to do with gcode is messy. It can also buy you a host of problems as a side effect (remember all the problems that occurred in earlier versions of mach when Code() changed from a sync construct to an async construct?).
c) The restriction to M123 files for names is a maintenance nightmare. You get lots of little files that are numbered. Not a good maintainable programming style.
Like everyone else, I had used this mechanism in MSM as it was the only game available (when all you own is a hammer, all problems look like a nail).
The first use I made of RunScript was to remove all uses of Code(“M123”). That was a significant improvement and removed a lot of side effect problems.
While doing that, Brian and I had many long conversations about potential ways to pass parameters to RunScript(). Again what should have conceptually been easy, turned out to be really hard. Passing parameters by name was not something I was going to get given how the Cypress was spliced in.
…but I had to have some parameters as I had used the P, R words with the Code() API to do “parameter passing”. The Mcode “calls” I was replacing already used simple parameters.
What to do? You use the tools you have at hand. (I’ve come to think of programming scripts in mach to be an exercise in ancient programming techniques. I pretend I’m a character in a time travel story that got sent back to the 1960s to program. Once you decide to go with the flow your stress level goes way down…. ).
I invented what is frankly a workable kludge: MSMRunScript(). This is an MSM internal wrapper for RunScript(). It uses MSM globals, coupled with implicit knowledge in a named script that will be “run” to implement “parameter passing”. Not elegant, but workable.
That’s not real pretty code. It’s Ok for internal MSM use, but not appropriate as an exposed API.
Key limitation: RunScript() has no parameter passing capability. I also really needed to be able to define Constants in one place and use them in multiple scripts. I needed a way to stop maintaining multiple copies of the MSM DRO and LED lists etc.
Pre-Processing….
2) #Expand
Brian and I decided that it was probably possible to add simple pre-processing for Cypress scripts. Because this would be pre-processing (done before the code is handed to the cypress black box at run time), it avoids any need to modify the Cypress engine or to have to extend the Cypress language syntax.
As usual, this turned out to be more work than initially expected – Brian made it eventually happen (thanks again to Brian).
Pre-processing to include source code has some advantages over RunScript():
1) it allows implementation of header files – that helps a lot with code maintenance.
2) You can create common, readable code that has parameters – and all the type checking etc works as it is just basic source code.
In MSM I typically put the definition and body of a function in a source file, use it wherever I want to and then add a #Expand at the end of the upper level script to pull in the function definition.
3) The Syntax used for #Expand is a simple version of what C #include does. We included the < > and “ ” concepts from C pre-processing.
The < > form allows the location of the included scripts to not have to be hard coded into the scripts themselves. This is a feature that MSM takes advantage of.
Refer to the programmers manual for #expand. The < > features were also done with Wizards in mind. The < > path expansion for expand files is smart enough to know to look in a wizard name dependent location if a wizard is loaded ….
Clear as mud?
Summary:
Use the feature that is most appropriate for what you are trying to accomplish.
Generally
• MSM uses RunScript to replace Code(“M123”)
• MSM uses RunScript as a run time means to access script to be supplied after MSM ships (Example: this is done to “call” AutoToolChanger hardware specific code) w/o needing to alter the MSM script source.
• MSM uses #Expand to create header files and MSM code modules for internal MSM reuse.
IMPORTANT NOTE:
MSM has a lot of internally defined and internally used code interfaces.
Users are free to read the MSM code, learn from it, plagiarize from it. The license terms for the MSM package permit you to do this.
HOWEVER…. (This is important)….
Not a single line of the MSM code is guaranteed to be stable across releases. Each and every line of code is subject to change without notice in a future release of MSM.
MSM does not provide a set of guaranteed programming interfaces.
(There are a couple of candidates that may get documented and become supported “MSM APIs”, but as of the beta release there are no committed MSM API interfaces.)
MSM DRO, LEDS, variable names, in fact anything, can change at any time.
It is NOT SAFE to assume you can “use” an internal MSM interface from outside MSM.
Dave
P.S. I had to edit this post. Does anyone know why when I type in M followed by 3 x's the board turns it into M********* ?
I changed my references to M123...
[edit: 3 x's in a row is on the list of naughty words, so it automagically gets blanked out with stars...
scott]