Here is an update on what I have been working on.
The current implementation of prefix assignments saves the state of the assigned parameters in Param instances, which it uses at the end of the execution of the prefixed command to restore the state of the parameters. My solution promotes these Param instances to real parameters by adding them to the parameter table. Thus, it becomes possible for pre-existing references to the assigned parameters to keep referring to the non-assigned version of the parameters during the execution of the prefixed command.
The parameters are added in the same scope as the one of the prefix assignments. This has the advantage that it also works for prefixed commands that don't start a new scope, like in "var=foo eval 'echo $var'". However, it implies that the same parameter may be added multiple times to the same scope, like in "local var=foo; var=bar echo 'echo $var'". This isn't actually as disruptive as it might first look. Expansions and assignments of regular parameters simply see the last added instance and ignore the other ones but the code for references can be tweaked to see a particular instance in a given scope, which is what enables pre-existing references to keep referring the non-assigned version of their parameter.
The implementation of this solution requires fine control over how and when references are initialized and resolved. This led to many tangents, some of which triggered further sub-tasks. Over the past few weeks, I have been mainly working on these items. I'm about to publish a number of patches from this work. The 3 described below are quite consequential.
- Refactor resolve_nameref() and define getparam(), loadparama(), and resolveparam(): A requirement for the solution described above is an enhanced version of resolve_nameref() that also returns the last followed reference. This work led to not only an improved reference resolution function but a mini-API to get, load and/or resolve parameters.
- Eliminate getparamnode() and the distinction between getnode() and getnode2(): Thanks to the mini-API of the previous patch it's possible to replace all paramtab->getnode() and paramtab->getnode2() calls with calls to functions that explicitly do what's needed, i.e., get, load, and/or resolve. Once that's done, paramtab->getnode() is no longer needed and can be changed to the same as paramtab->getnode2(), i.e., simply return the node/parameter with the specified name.
- Eliminate paramtab and rename realparamtab into paramtab: The previous patch removes a significant part of all the references to paramtab. Many of the remaining ones occur in contexts where paramtab is necessarily equal to realparamtab. To my great surprise, I discovered that createparam() and unsetparam() are in fact the only functions that are ever called where paramtab may be different from realparamtab. This patch parametrizes these functions with an explicit HashTable, then eliminates paramtab, and finally renames realparamtab into paramtab.
Philippe