alma
alma copied to clipboard
Create really useful instrumentation for Alma
Whenever I need to go in and debug some scoping/lookup bug (as I am currently), I find myself injecting say
statements in always the same places.
Here's what I would like (exemplified with examples/name.007
):
macro name(expr) { # scope_1 -- invoked 3 times
# [ `expr` found in current scope (macro `name`) ]
# [ `Q::Postfix::Property` found in builtins_scope ]
if expr ~~ Q::Postfix::Property {
# invocations 2 and 3 took this branch
# [ `expr` found in scope_1 (macro `name`) ]
# [ `expr` found in scope_1 (macro `name`) ]
expr = expr.property;
}
# [ `expr` found in current scope (macro `name`) ]
# [ `Q::Identifier` found in builtins_scope ]
if expr !~~ Q::Identifier {
# never reached
# [ `Exception` found in builtins_scope ]
# [ `expr` found in scope_1 (macro `name`) ]
# [ `type` found in builtins_scope ]
throw new Exception {
message: "Cannot turn a " ~ type(expr) ~ " into a name"
};
}
# [ `expr` found in scope_1 (macro `name`) ]
return quasi { expr.name };
}
my info = {
foo: "Bond",
bar: {
baz: "James Bond"
},
};
# [ `info` found in current scope ]
# [ `name` found in current scope ]
# [ `say` found in builtins_scope ]
say(name(info)); # info
# ===> macro call `name(info)` expanded to: "info"
#
# [ `info` found in current scope ]
# [ `name` found in current scope ]
# [ `say` found in builtins_scope ]
say(name(info.foo)); # foo
# ===> macro call `name(info.foo)` expanded to: "foo"
#
# [ `info` found in current scope ]
# [ `name` found in current scope ]
# [ `say` found in builtins_scope ]
say(name(info.bar.baz)); # baz
# ===> macro call `name(info.bar.baz)` expanded to: "baz"
#
A scope only needs to be named if a variable is ever looked up in it. Now that I think about it, perhaps scope_macro_name
would be an even better name for that scope. Numbers can be used only if/when there are collisions.
Those are all static scopes. For each activation of a routine there will also be an active scope. I don't know how much of that information needs to be shown -- I didn't need it above. In an actual debugging situation it might be useful.
There should probably be an option to turn the variable lookups off. Or to only show them for some chosen variable.
The macro expansion instrumentation is a killer feature, of course. Note that that's a Qtree fragment being rendered as code. In fact, that might be a good thing to do with the entire script; just render it and pretty-print it.
That, and a pony. :carousel_horse:
I forgot to add, though maybe that's obvious, that I'd like this to be a kind of backend.
Here's a new attempt, highlighting what I actually go looking for during scope/frame/lookup debugging: which frames are declared where, and which variables are looked up in which frames.
I attempted to keep the noise at a minimum this time. Only frames that are used in an interesting way are shown. "Interesting" here means "a variable is looked up in a deeper scope", and "deeper" has some definition that includes quasi
scopes (and subs) but not if
statements.
Internally, the instrumentation would keep track of all the .WHICH
of all the frames, and then keep only the interesting ones, number them in source code order as below, and show them as comments attached to the appropriate block. Having this work properly would eliminate lots of debug printing and head-scratching. This is what I was trying for the first time around.
# active frame #1
1. macro name(expr) {
# static frame #2
# call A from line 20: frame #3
# call B from line 21: frame #4
# call C from line 22: frame #5
2. if expr ~~ Q::Postfix::Property {
# calls B and C took this path. call A didn't.
3. expr = expr.property;
4. }
5. if expr !~~ Q::Identifier {
6. throw new Exception {
7. message: "Cannot turn a " ~ type(expr) ~ " into a name"
8. };
9. }
# 'expr' looked up in frame #3 (macro-expanded into `name(info)`)
# 'expr' looked up in frame #4 (macro-expanded into `name(info.foo)`)
# 'expr' looked up in frame #5 (macro-expanded into `name(info.bar.baz)`)
10. return quasi { expr.name };
11. }
12.
13. my info = {
14. foo: "Bond",
15. bar: {
16. baz: "James Bond"
17. },
18. };
19.
20. say(name(info)); # info
21. say(name(info.foo)); # foo
22. say(name(info.bar.baz)); # baz
I initially wrote "call A" etc on the annotations above line 10, but that's incorrect. That lookup does not happen within the bounds of call A etc. It happens when we run lines 20..22, i.e. very late. Concievably the instrumentation could still backwards-attach that to the respective calls, but perhaps better not to.
Also, I don't think that the macro instrumentation can be as nice with expansions as I imagined in the first example, the expr.name
part of the quasi still doesn't run until runtime, and until then all we know about it (in general) is that it's some code, not that it evaluates to a string.
The successful implementation of this issue has been held up, at least partly, by the revelations in #212.