Hi,

Some code is very “active”, executed all the time, e.g. collections, graphics… 
or imagine if you work on the compiler or debugger.

It would really be nice if we could test a change before accepting it. 
Something like “Accept for Test” where magically the original method
does not change, yet, when running tests, the version of the code we accepted 
for testing is executed.

So how do we implement that?

1) menu in the browser. I did a quick hack, a Suggestions AST menu shows for 
all nodes (to be replaced with the “correct” way later).

SugsSuggestion subclass: #SugsAcceptForTest
        instanceVariableNames: ''
        classVariableNames: ''
        package: 'SmartSuggestions-Suggestion’

label
        ^'Accept for test’ 

So, now we can do everything in the #execute method.

What do we need?

1) How we know that we are in a test?  —> CurrentExecutionEnvironment value 
isTest
2) can we compile the buffer even if there are syntax errors (kind of what I 
expect to happen in non-accepted code)?
        —> yes, remember, we use the same Parser for syntax highlighting and we 
extended code-generation for SyntaxError Nodes
               in the past (it raises a syntax error at runtime, isn’t that 
kind off odd in a fun way?).

        newMethod := context selectedClass compiler
                source: context code;
                options: #(+ optionParseErrors);
                compile.

3) Now how to we “hook into” the execution of the original unchanged method? We 
use a MetaLink. We install it as a before link on
    the RBMethodNode, the meta-object is a block. This block gets as arguments 
the thisContext and the arguments of the original method
   and then just does what you want it do do: if we are in test, return the 
result of the execution of the method we just compiled.
   (Closures are fun, we can just reference the outer temp newMethod, no need 
to store it anywhere else explicitly):

                 [ :aContext :args | 
                        CurrentExecutionEnvironment value isTest
                                ifTrue: [ aContext return: (newMethod 
valueWithReceiver: aContext receiver arguments: args) ] ];

Thus, all the code is this:
        
execute
        | newMethod metaLink |
        “we compile a new method from the not-accepted text editor. Allow 
syntax errors"
        newMethod := context selectedClass compiler
                source: context code;
                options: #(+ optionParseErrors);
                compile.
        "the link executes the method we just created and returns"
        metaLink := MetaLink new
                metaObject: [ :aContext :args | 
                        CurrentExecutionEnvironment value isTest
                                ifTrue: [ aContext return: (newMethod 
valueWithReceiver: aContext receiver arguments: args) ] ];
                selector: #value:value:;
                arguments: #(context arguments).
        context selectedMethod ast link: metaLink

Cleanup we do not care as everything will be GCed as soon as we accept the 
method for real.

And it works! I did not test it too much, but it seems to do what it is 
supposed to do. (but more tests and testing are for sure needed)
https://github.com/pharo-project/pharo/pull/1182

Next step: move it to a better place in the menu.

        Marcus


Reply via email to