I am building a custom small interpreted script language and everything is working just fine except the scoping. For the actual execution I am using a visitor pattern:
I modified the pattern to pass through the Variable Table:
public void visit(ProgrammTree proTree){
VariableTable vt = new VariableTable();
foreach (var t in proTree.getChildren()) {
t.accept(this, vt);
}
}
And here is where the problem starts:
public void visit(WhileTree whiletree, VariableTable vt) {
var cond = (ConditionTree)whiletree.getChild(0);
while (cond.accept(this, vt).toBoolean()) {
var clonedSubTable = new VariableTable(vt)
foreach (Tree t in whiletree.getChildren()) {
t.accept(this, clonedSubTable );
}
}
}
Problem is that changes within the loop are not performed in the outer scope. Do you have a smart way to implement this?
You left a couple of things a bit vague, so I'm going to make the following assumptions (please point out any that are wrong):
So under these assumption the problem is that any assignments done using the cloned table won't be visible in the original table even in cases where the assigned-to variable was already present in the original table.
To fix this, there are multiple approaches:
You can make your table map variables to memory locations rather than values. You'd then have another table (or just a plain array) that maps memory locations to values. This way the table-entry for a variable would never change: Once the variable is defined, it gets a memory address and that address isn't going to change until the variable dies. Only the value at the address may change.
A quick-and-dirty alternative to that approach that keeps you from having to manage your own memory would be to add a mutable wrapper around your values, i.e. a ValueWrapper
class with a setValue
method. Assuming that your cloned table is a shallow copy of the original, that means that you can call setValue
on an entry of the cloned table and, if that entry was already present in the original table, the change will also be reflected in the original table.
The solution that keeps closest to your current code would be to turn your table into a linked structure. Then new VariableTable(vt)
would not actually copy anything, but simply create a new empty table with a parent link pointing to the original table. Any new entries would be inserted into the new table, but accesses to old entries would simply be propagated to the parent table.
Even if you choose to go with options 1 or 2 to solve your current problem, using a parent link instead of copying the table constantly would be a good idea anyway for performance reasons.
The downside of only going with solution 3 would be that you'll run into similar problems again when you implement closures. So it really only fixes your exact current problem.
The upside of solution 1 is that it allows you full control over your memory, so you have free hand in implementing features related to memory in any way you want. The down side is the same.