If we want probe results (for example) to be available in parameters, we need a way to ensure that the interpreter doesn't read ahead and use the parameter value before the probe move finishes.
Here is one possible solution to that:
Every canonical command has two lists associated with it, a list of parameters that it wants to read, and a list of parameters it wants to write. G-code lines of the form #100 = [#100+#101*2] also have lists, which are created on the fly when the expression is parsed - in this case it reads parameters 100 and 101, and writes parameter 100.
Every canonical command issued, as well as every expression executed, is given a serial number, a 64 bit integer. That is large enough to ensure that the numbers will be monotonically increasing during any run.
Each parameter has associated with it a "latest writer" field, which accepts a serial number.
The interpreter also has a "last executed" variable, which contains the serial number of the latest command to finish executing.
In the interpreter, when it is time to evaluate an expression, or to execute a canonical, the code would loop through the list of "params to be read" for that operation. If the "latest writer" fields for each of those parameters are equal or less than "last executed", the interp knows that the parameters are up to date, and the operation would proceed. If not, the interpreter would sleep and retry that field later.
Once the operation is allowed to start, it would write its own serial number into the "latest writer" fields of every parameter on its "params to write" list. That would force subsequent operations that want to read those parameters to wait.
Once an operation finishes (which may be much later due to queuing), it would update the interpreter's "last executed" variable. This is where I get a little fuzzy. Suppose operation 10 is queued, and operations 11 thru 20 are not (simple math expressions). If 11 thru 18 do not depend on anything that is written by 10, they can (and should) execute _before_ 10 finishes. If 19 does depend on something that 10 writes, it will have to wait until 10 completes. In particular, it will wait until "last executed" is greater or equal to 10. But if we allow 11 thru 18 to update "last executed" before 10 finishes, we have a problem. I know this can be solved, I just don't have a nice O(1) solution at the moment.
I just realized that assignments don't need "params to write", since they are executed entirely in the interpreter. The need "params to read" to ensure that they are using up-to-date input values, but their outputs are guaranteed to be written to the parameters before the next statement starts. Only queued items need to mark their "params to write" with their serial numbers.
Another thought - this could be used to implement pre- and post-conditions. If we dedicate a parameter to each possible condition, then operations that need to have a condition satisified simply list that parameter under their "params to read". Likewise, operations that make a condition true would list that parameter under "params to write".