Continuing the discussion from Updating the Snap! Manual:
Long Quote
Alright, so based on this post I’ve been trying to make macros in Snap, and here is what I’ve got:
I started by playing around with the scoping of variables in custom blocks. I managed to create this:
which results in this:
The definition of “inherit caller context” first declares the existing script vars (aka “num”) (you’ll see why I had to use the create variables extension later) in the caller’s context and then runs its continuation in the caller’s context as well:
That does have side effects, however:
From there I realized my first problem - the means by which I had implemented the “inherit caller context” block didn’t allow for reporting:
Thus, I devised a new, separate block for reporting in the caller:
The next step was getting the creation of script variables to work, because, as it was, this was what happened:
So, I began experimenting with ways to create a script variable in the caller’s context. However, neither this:
nor this:
actually worked. At last I realized why. Run (and tell) create their own kind of sub-context which notations like IF do not. This is best explained in pictures:
IF is doing something different from RUN - the code inside the c-shape doesn’t have a new sub-context - it has the same context as the code outside. This is good, expected behaviour. But it also means we can’t create script variables in a caller’s context. If we were to do this, for example:
We don’t gain access to the main script’s context, we create a new branch context from the main script’s caller. However, I have encountered this issue before and know the solution - the create variables library:
This also works from within custom blocks:
Now all I had to do was create a block to replace every SCRIPT VARIABLES block with the block from the create variables library, as well as every report with my version of report:
Alright, let’s take stock. We are now ready to create a block that will take a desired definition and make it function in the caller - here’s an example of what that definition might look like as written by the user:
and then once modified via metaprogramming:
And it functions!
However, it is at this point that we start having problems. Because the Create Variables library’s script variable scoping works differently (which we need), there are some unintended side effects - script variables are created in the outermost context, not the desired caller context. Let’s take our “create foo variable” block from earlier, and see what happens when we wrap it in a ring:
Compare this to the built in script variables:
There are more problems too. I don’t know if the behaviour shown above is intended or not, so it could be patched at a later date. Additionally, let’s take a look at the IF/ELSE example @bh gave. The individual c-shape slots need to have their script variables and report blocks replaced with my versions, and this is something we have to do at runtime this time:
This means that our if notation will be slowed down and won’t be able to be visible stepped for debugging. This is far from ideal. We need a better way to run a script without creating a new context for it - like the primitive notations…
I tried modifying the definition of the primitive IF/ELSE to make the first input non-static, but this, unsurprisingly, didn’t work. The primitive definition, when toggled on, isn’t actually taking a ringified script as input, but something else:
This would be the case for all primitive notations. I needed a library notation instead. But most library notations are built in Snap and thus have the same problem:
I needed a notation that used an EXTENSION block for its definition. And then it hit me - SAFELY TRY:
I noted that I should run the script in the second input to avoid catching errors that should have been thrown, and then proceeded to make a “run w/o new context” block:
Taking this back to our IF/ELSE example, it works as well:
Reporting works too:
However, here is where things get a little tricky. You probably only want this kind of behaviour if your input is a c-shape. If it is a command or reporter ring, it might have input parameters and should probably behave like the built in RUN. Thus, I have written the REWRITE block to create a definition like this:
This also ensures that we don’t have to worry about how to add inputs to this version of run - if you’re adding inputs, that means it was in a grey ring and should get its own sub-context (and needs to have one for those pesky parameter not-variables). Here is how you use REWRITE at this time:
and its result:
I’ll be sharing the project and adding the hygienic bit soon.























































