One of the problems beginners often have with the Suneido WorkSpace is that local variables “disappear” between Run commands. For example, if you Run:
a = 5
and then (separately) Run:
a * 2
you’ll get “uninitialized variable: a”. This is because each “Run” is executed with Eval, which turns the code into a function:
function () { a * 2 }
(You can see this in the debugger if you Run something with an error.) The function is then called, and local variables (like “a”) only last for one function call.
This also makes it harder to write examples in the documentation. You want to write things like:
s = "hello world" s.Substr(0, 2) => "he" s.Substr(-2) => "ld"
But you can’t actually Run it that way because when you tried to Run “s.Substr(-2)” you’d get “uninitialized variable: s”.
One way around this is by using the “Suneido” global object to store values. For example:
Suneido.a = 5
But this is awkward, and using the Suneido object this way is not a good idea – you could easily overwrite important values stored there.
It would be nice if, somehow, we could “save” the values of the local variables after each Run, and then “restore” them at the beginning of the next Run. The question is how to do this.
For debugging, Suneido has a Locals function that returns an object containing all the local variables. Perhaps we could do something like:
a = 5 Suneido.vars = Locals(0)
Unfortunately, that won’t work when we want to return a value, or if the code returns before the end. With enough effort, you could probably make this work, but it wouldn’t be easy.
How else can we capture the variables? The answer lies in the ability of a Suneido block to “capture” its context. A block is like a function but it executes in the context of the containing function. A block that “outlasts” the function call that created it “keeps” its context. So we can do:
Suneido.wsget = { Locals(0) } a = 5
Then if we (separately) Run:
(Suneido.wsget)() => #(a: 5, this: /* function */)
(We need the parenthesis around Suneido.wsget because wsget is a data member, not a class method.)
This gives us a way to save the local variables – we just add “Suneido.wsget = { Locals(0) }” to the beginning of the code to be Run, and then call Suneido.wsget (the block) afterwards. So now we just need a way to “restore” the local variables at the start of the next Run. At first I thought we could just loop through the variables and add assignments, like:
a = 5 a * 2
This works for simple values, but it won’t work for things like transactions, or complex objects, or even very long strings. Instead we need to use the actual value. One option is to put the variables into the Suneido object and then do something like:
a = Suneido.wsvars.a a * 2
To summarize, when the user Run’s “a * 2” after previously running “a = 5”, we’re going to actually Eval:
Suneido.wsget = { Locals(0) } a = Suneido.wsvars.a a * 2
and afterwards, we’ll update the variables with:
.vars = (Suneido.wsget)()
So the WorkSpace On_Run code will change from:
x = s.Eval()
to something like:
for m in .vars.Members() s = m $ " = Suneido.wsvars." $ m $ "\n" $ s s = "Suneido.wsget = { Locals(0) }\n" $ s Suneido.wsvars = .vars x = s.Eval() Suneido.Delete("wsvars") .vars = (Suneido.wsget)().Delete('this') Suneido.Delete("wsget")
(Since there may be multiple WorkSpace’s open, we don’t want to leave things in the Suneido object where they may get overridden.)
For an added feature, we can save the result (return value) in the variables so it can be used later:
.vars.z_ = x
Of course, it would be nice if the user could see what variables are set. Let’s add an InspectControl to the bottom of the WorkSpace. And we’re probably going to want a way to clear the variables. We could put another clear button on the WorkSpace toolbar, but it might be nicer to put the clear buttons with their panes, something like:
(The appearance could probably be improved, but this was quick to throw together.)
I won’t describe the user interface code since that’s not the topic of this article. You can download the code if you want to see how it was done.
My initial version of clear was simply:
On_Clear_Variables() { .vars = #() .inspect.Reset(#()) }
But some kinds of values should be “closed” or “released” when you’re done with them. Currently, this is the user’s responsibility, but why not do it automatically in clear:
for val in .vars switch (Type(val)) { case 'Transaction' : val.Rollback() case 'COMobject' : val.Release() default : try val.Close() }
(Since there are quite a few values that use Close, it was easier just to try to Close.)
And we might as well clean up in Destroy as well:
Destroy() { .On_Clear_Variables() super.Destroy() }
(Don’t forget to call super.Destroy when you override Destroy.)
This doesn’t completely handle the problem. You could do “t = Transaction()” and then “t = 0” and the transaction wouldn’t get closed. But it handles most cases.
While I was working on this, I noticed that WorkSpace was not saving and restoring the splitter position. With two splits now, it probably should. It was easy to fix:
GetState() { return Object(text: .editor.Get(), editor_state: .editor.GetState(), vert_split: .Vert.VertSplit.GetSplit(), horz_split: .Vert.VertSplit.HorzSplit.GetSplit()) } SetState(state) { if (not Object?(state)) return if (state.Member?('text')) .editor.Set(state.text); if (state.Member?('editor_state')) .editor.SetState(state.editor_state) if (state.Member?('vert_split')) .Vert.VertSplit.SetSplit(state.vert_split) if (state.Member?('horz_split')) .Vert.VertSplit.HorzSplit.SetSplit(state.horz_split) }
It might also be nice to be able to hide or show the Output and Variables panes. Or even better, to be able to drag them to different positions or stack them on top of each other in a tabbed layout. But that will have to wait for another day (or another programmer!).
If you want to try this out with the June 30, 2004 release (later releases will include this improvement), you can download the modifications from wshack.zip. This contains wshack.txt with the modified records. You can import this into a new library (e.g. wshacklib), or if you’re brave, right into stdlib.