Very cool. Impressive combination of unique Pharo features.

> On 4 Apr 2018, at 18:02, Marcus Denker <marcus.den...@inria.fr> wrote:
> 
> 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