<div dir="ltr">Virtual high-five, Eliot, for Rectangular Block.   :)<div><br></div><div>I'm sure you already have your own version, but just in case, I just copied what I've been using to pretty-print in Rectangular Block to the Inbox.  It's Compiler-cmm.329.  It's not quite perfect, but as close as I could get it.</div><div><br></div><div>Since it's from 2016, it needs to be merged instead of loaded, of course.  No conflicts.</div><div><br></div><div> - Chris</div><div><br></div><div><br></div><div><br></div><div><br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Tue, Oct 8, 2019 at 1:08 PM Eliot Miranda <<a href="mailto:eliot.miranda@gmail.com" target="_blank">eliot.miranda@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div dir="ltr">Hi Marcel,</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, Sep 4, 2019 at 8:04 AM <<a href="mailto:commits@source.squeak.org" target="_blank">commits@source.squeak.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">A new version of Compiler was added to project The Inbox:<br>
<a href="http://source.squeak.org/inbox/Compiler-mt.410.mcz" rel="noreferrer" target="_blank">http://source.squeak.org/inbox/Compiler-mt.410.mcz</a><br>
<br>
==================== Summary ====================<br>
<br>
Name: Compiler-mt.410<br>
Author: mt<br>
Time: 4 September 2019, 5:03:52.834738 pm<br>
UUID: 7ff9d1f8-5f7a-4077-b11b-ede80ada7d13<br>
Ancestors: Compiler-TraitTest.409<br>
<br>
Fixes ifNil:ifNotNil: decompilation. Please review.<br></blockquote><div><br></div><div>This looks good.  Forgive me but I must say that I wish you would use rectangular blocks (a la Beck Smalltalk Best Practice Patterns).  I am a visual thinker and find non-rectangular blocks at best irritating.  Blocks are objects, not simply syntax, and the use of non-rectangular blocks is reminiscent of curly bracket languages, where brackets merely delimit a sequence of statements rather than encode an object.</div><div><br></div><div>On etiquette I tend to leave formatting as I find it in packages with definite owners and clear preferences.  In the Compiler I have a clear preference ;-)</div><div><br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
- Only decompile ifNil:ifNotNil: if temps are not closured across nested blocks. This is the same behavior as #to:(by:)do:, which does not restore #to:(by:)do: if the 'var' or 'limit' are in an outer (outer?) scope. Only relevant if programmers type the optimized source code themselves.<br>
- Note that I created a new method in DecompilerConstructor to pass 'tempReadCounts'. #to:(by:)do: is reconstructed in Decompiler, which already has access to 'tempReadCounts'. See Decompiler >> #jump:if: and #convertToDoLoop:.<br>
<br>
=============== Diff against Compiler-TraitTest.409 ===============<br>
<br>
Item was added:<br>
+ ----- Method: AssignmentNode>>ifNilTemporary (in category 'private') -----<br>
+ ifNilTemporary<br>
+       "(temp := object) == nil ifTrue: [...] ifFalse: [...]"<br>
+       <br>
+       ^ self variable!<br>
<br>
Item was added:<br>
+ ----- Method: AssignmentNode>>ifNilValue (in category 'private') -----<br>
+ ifNilValue<br>
+       "(temp := object) == nil ifTrue: [...] ifFalse: [...]"<br>
+       <br>
+       ^ self value!<br>
<br>
Item was changed:<br>
  ----- Method: BlockNode>>printTemporaries:on:doPrior: (in category 'printing') -----<br>
  printTemporaries: tempSequence on: aStream doPrior: aBlock<br>
        "Print any in-scope temporaries.  If there are any evaluate aBlock<br>
         prior to printing.  Answer whether any temporaries were printed."<br>
        | tempStream seen |<br>
        tempSequence ifNil:<br>
                [^false].<br>
        tempStream := (String new: 16) writeStream.<br>
        "This is for the decompiler which canmot work out which optimized block a particular temp is<br>
         local to and hence may produce diplicates as in<br>
                expr ifTrue: [| aTemp | ...] ifFalse: [| aTemp | ...]"<br>
        seen := Set new.<br>
        tempSequence do:<br>
                [:tempNode |<br>
                tempNode isIndirectTempVector<br>
                        ifTrue:<br>
                                [tempNode remoteTemps do:<br>
                                        [:tempVariableNode|<br>
                                         (tempVariableNode scope >= 0<br>
+                                         and: [<br>
+                                               "This is for the deocmpiler which may create a block arg when converting<br>
+                                               a ifTrue:ifFalse: into a ifNil:ifNotNil: but won't remove it from temporaries"<br>
+                                               tempVariableNode isBlockArg not<br>
+                                         and: [(seen includes: tempNode key) not]]) ifTrue:<br>
-                                         and: [(seen includes: tempNode key) not]) ifTrue:<br>
                                                [tempStream space; nextPutAll: (seen add: tempVariableNode key)]]]<br>
                        ifFalse:<br>
                                [(tempNode scope >= -1<br>
                                  and: ["This is for the decompiler which may create a block arg when converting<br>
+                                               a while into a to:do: but won't remove it from temporaries"<br>
-                                               a while into a to:do: but won't remove it form temporaries"<br>
                                           tempNode isBlockArg not<br>
                                  and: [(seen includes: tempNode key) not]]) ifTrue:<br>
                                        [tempStream space; nextPutAll: (seen add: tempNode key)]]].<br>
        tempStream position = 0 ifTrue:<br>
                [^false].<br>
        aBlock value.<br>
        aStream nextPut: $|; nextPutAll: tempStream contents; space; nextPut: $|.<br>
        ^true!<br>
<br>
Item was changed:<br>
  ----- Method: Decompiler>>jump:if: (in category 'instruction decoding') -----<br>
  jump: dist if: condition<br>
<br>
        | savePc sign elsePc elseStart end cond ifExpr thenBlock elseBlock<br>
          thenJump elseJump condHasValue isIfNil saveStack |<br>
        lastJumpIfPcStack addLast: lastPc.<br>
        stack last == CascadeFlag ifTrue: [^ [self case: dist] ensure: [lastJumpIfPcStack removeLast]].<br>
        elsePc := lastPc.<br>
        elseStart := pc + dist.<br>
        end := limit.<br>
        "Check for bfp-jmp to invert condition.<br>
        Don't be fooled by a loop with a null body."<br>
        sign := condition.<br>
        savePc := pc.<br>
        self interpretJump ifNotNil:<br>
                [:elseDist|<br>
                 (elseDist >= 0 and: [elseStart = pc]) ifTrue:<br>
                         [sign := sign not.  elseStart := pc + elseDist]].<br>
        pc := savePc.<br>
        ifExpr := stack removeLast.<br>
        (isIfNil := stack size > 0 and: [stack last == IfNilFlag]) ifTrue:<br>
                [stack removeLast].<br>
        saveStack := stack.<br>
        stack := OrderedCollection new.<br>
        thenBlock := self blockTo: elseStart.<br>
        condHasValue := hasValue or: [isIfNil].<br>
        "ensure jump is within block (in case thenExpr returns)"<br>
        thenJump := exit <= end ifTrue: [exit] ifFalse: [elseStart].<br>
        "if jump goes back, then it's a loop"<br>
        thenJump < elseStart<br>
                ifTrue:<br>
                        [| blockBody blockArgs savedReadCounts blockBodyReadCounts selector |<br>
                         "Must be a while loop...<br>
                          thenJump will jump to the beginning of the while expr.  In the case of while's<br>
                          with a block in the condition, the while expr should include more than just<br>
                          the last expression: find all the statements needed by searching for the node<br>
                          with the relevant pc."<br>
                        stack := saveStack.<br>
                        savedReadCounts := tempReadCounts copy.<br>
                        pc := thenJump.<br>
                        blockBody := self statementsTo: elsePc.<br>
                        blockBodyReadCounts := tempReadCounts.<br>
                        savedReadCounts keysAndValuesDo:<br>
                                [:temp :count|<br>
                                 blockBodyReadCounts at: temp put: (blockBodyReadCounts at: temp) - count].<br>
                        tempReadCounts := savedReadCounts.<br>
                        "discard unwanted statements from block"<br>
                        blockBody size - 1 timesRepeat: [statements removeLast].<br>
                        blockArgs := thenBlock statements = constructor codeEmptyBlock statements<br>
                                                        ifTrue: [#()]<br>
                                                        ifFalse: [{ thenBlock }].<br>
                        selector := blockArgs isEmpty<br>
                                                        ifTrue: [sign ifTrue: [#whileFalse] ifFalse: [#whileTrue]]<br>
                                                        ifFalse: [sign ifTrue: [#whileFalse:] ifFalse: [#whileTrue:]].<br>
                        statements addLast:<br>
                                (constructor<br>
                                        codeMessage: (constructor codeBlock: blockBody returns: false)<br>
                                        selector: (constructor codeSelector: selector code: #macro)<br>
                                        arguments: blockArgs).<br>
                        pc := elseStart.<br>
                        selector == #whileTrue: ifTrue:<br>
                                [self convertToDoLoop: blockBodyReadCounts]]<br>
                ifFalse:<br>
                        ["Must be a conditional..."<br>
                        elseBlock := self blockTo: thenJump.<br>
                        elseJump := exit.<br>
                        "if elseJump is backwards, it is not part of the elseExpr"<br>
                        elseJump < elsePc ifTrue:<br>
                                [pc := lastPc].<br>
                        cond := isIfNil<br>
                                                ifTrue:<br>
                                                        [constructor<br>
                                                                codeMessage: ifExpr ifNilReceiver<br>
                                                                selector: (constructor<br>
                                                                                        codeSelector: (sign ifTrue: [#ifNotNil:] ifFalse: [#ifNil:])<br>
                                                                                        code: #macro)<br>
                                                                arguments: (Array with: thenBlock)]<br>
                                                ifFalse:<br>
+                                                       [(sign ifTrue: [{elseBlock. thenBlock}] ifFalse: [{thenBlock. elseBlock}]) in: [:args |<br>
+                                                               (constructor<br>
+                                                                       decodeIfNilWithReceiver: ifExpr<br>
+                                                                       selector: #ifTrue:ifFalse:<br>
+                                                                       arguments: args<br>
+                                                                       tempReadCounts: tempReadCounts) ifNil: [<br>
+                                                                               constructor<br>
+                                                                                       codeMessage: ifExpr<br>
+                                                                                       selector: (constructor codeSelector: #ifTrue:ifFalse: code: #macro)<br>
+                                                                                       arguments:       args]]].<br>
-                                                       [constructor<br>
-                                                               codeMessage: ifExpr<br>
-                                                               selector: (constructor codeSelector: #ifTrue:ifFalse: code: #macro)<br>
-                                                               arguments:      (sign<br>
-                                                                                               ifTrue: [{elseBlock. thenBlock}]<br>
-                                                                                               ifFalse: [{thenBlock. elseBlock}])].<br>
                        stack := saveStack.<br>
                        condHasValue<br>
                                ifTrue: [stack addLast: cond]<br>
                                ifFalse: [statements addLast: cond]].<br>
        lastJumpIfPcStack removeLast.!<br>
<br>
Item was changed:<br>
  ----- Method: DecompilerConstructor>>codeMessage:selector:arguments: (in category 'constructor') -----<br>
  codeMessage: receiver selector: selector arguments: arguments<br>
        | symbol |<br>
        symbol := selector key.<br>
        (self<br>
                decodeLiteralVariableValueDereferenceWithReceiver: receiver<br>
                selector: symbol<br>
                arguments: arguments) ifNotNil: [:node| ^node].<br>
+ <br>
-       (self decodeIfNilWithReceiver: receiver<br>
-                       selector: symbol<br>
-                       arguments: arguments) ifNotNil: [:node| ^node].<br>
        ^MessageNode new<br>
                        receiver: receiver selector: selector<br>
                        arguments: arguments<br>
                        precedence: symbol precedence!<br>
<br>
Item was removed:<br>
- ----- Method: DecompilerConstructor>>decodeIfNilWithReceiver:selector:arguments: (in category 'constructor') -----<br>
- decodeIfNilWithReceiver: receiver selector: selector arguments: arguments<br>
-       receiver ifNil: [ ^nil ].               "For instance, when cascading"<br>
-       selector == #ifTrue:ifFalse:<br>
-               ifFalse: [^ nil].<br>
-       (receiver isMessage: #==<br>
-                               receiver: nil<br>
-                               arguments: [:argNode | argNode == NodeNil])<br>
-               ifFalse: [^ nil].<br>
-       ^ (MessageNode new<br>
-                       receiver: receiver<br>
-                       selector: (SelectorNode new key: #ifTrue:ifFalse: code: #macro)<br>
-                       arguments: arguments<br>
-                       precedence: 3)<br>
-               noteSpecialSelector: #ifNil:ifNotNil:!<br>
<br>
Item was added:<br>
+ ----- Method: DecompilerConstructor>>decodeIfNilWithReceiver:selector:arguments:tempReadCounts: (in category 'constructor') -----<br>
+ decodeIfNilWithReceiver: receiver selector: selector arguments: arguments tempReadCounts: tempReadCounts<br>
+       <br>
+       | node temp |<br>
+       receiver ifNil: [ ^nil ].               "For instance, when cascading"<br>
+       selector == #ifTrue:ifFalse:<br>
+               ifFalse: [^ nil].<br>
+                               <br>
+       (receiver isMessage: #==<br>
+                               receiver: nil<br>
+                               arguments: [:argNode | argNode == NodeNil])<br>
+               ifFalse: [^ nil].<br>
+               <br>
+       "Like #to:(by:)do:, support only local temps."<br>
+       (((temp := receiver ifNilTemporary) isNil or: [tempReadCounts includesKey: temp]) or: [<br>
+               "What about 'object ifNotNil: [:o | ]', which as not read the blockArg? Just check that there is no remote vector pointing to it."<br>
+               tempReadCounts keys noneSatisfy: [:otherTemp |<br>
+                       otherTemp isIndirectTempVector<br>
+                               ifTrue: [otherTemp remoteTemps anySatisfy: [:remoteTemp | remoteTemp name = temp name]]<br>
+                               ifFalse: [otherTemp name = temp name]]<br>
+                       ])<br>
+               ifFalse: [^ nil].<br>
+               <br>
+       node := (MessageNode new<br>
+                       receiver: receiver<br>
+                       selector: (SelectorNode new key: #ifTrue:ifFalse: code: #macro)<br>
+                       arguments: arguments<br>
+                       precedence: 3).<br>
+ <br>
+       "Reconfigure the message node to #ifNil:ifNotNil:. Note that original* instance variables keep their optimized format. See MessageNode >> #printIfNilNotNil:indent:."   <br>
+       node<br>
+               noteSpecialSelector: #ifNil:ifNotNil:;<br>
+               selector: (SelectorNode new key: #ifNil:ifNotNil:).<br>
+       <br>
+       temp ifNil: [^ node].<br>
+       temp isTemp ifFalse: [^ node].<br>
+       <br>
+       (arguments second isJust: NodeNil) not ifTrue: [<br>
+                       temp beBlockArg.<br>
+                       node arguments: {<br>
+                               arguments first.<br>
+                               arguments second copy arguments: { temp }; yourself } ].<br>
+                               <br>
+       ^ node!<br>
<br>
Item was added:<br>
+ ----- Method: MessageNode>>ifNilTemporary (in category 'private') -----<br>
+ ifNilTemporary<br>
+ <br>
+       ^ self ifNilReceiver ifNilTemporary!<br>
<br>
Item was changed:<br>
  ----- Method: MessageNode>>printIfNilNotNil:indent: (in category 'printing') -----<br>
  printIfNilNotNil: aStream indent: level<br>
<br>
+       (arguments first isJust: NodeNil) ifTrue: [<br>
+               self printReceiver: receiver ifNilReceiver ifNilValue on: aStream indent: level.<br>
+               ^ self printKeywords: #ifNotNil:<br>
-       self printReceiver: receiver ifNilReceiver on: aStream indent: level.<br>
- <br>
-       (arguments first isJust: NodeNil) ifTrue:<br>
-               [^ self printKeywords: #ifNotNil:<br>
                                arguments: { arguments second }<br>
                                on: aStream indent: level].<br>
+                       <br>
+       (arguments second isJust: NodeNil) ifTrue: [<br>
+               self printReceiver: receiver ifNilReceiver on: aStream indent: level.<br>
+               ^ self printKeywords: #ifNil:<br>
-       (arguments second isJust: NodeNil) ifTrue:<br>
-               [^ self printKeywords: #ifNil:<br>
                                arguments: { arguments first }<br>
                                on: aStream indent: level].<br>
+       <br>
+       self printReceiver: receiver ifNilReceiver ifNilValue on: aStream indent: level.<br>
        ^ self printKeywords: #ifNil:ifNotNil:<br>
                        arguments: arguments<br>
                        on: aStream indent: level!<br>
<br>
Item was changed:<br>
  ----- Method: MessageNode>>printWithClosureAnalysisIfNilNotNil:indent: (in category 'printing') -----<br>
  printWithClosureAnalysisIfNilNotNil: aStream indent: level<br>
<br>
+       (arguments first isJust: NodeNil) ifTrue: [<br>
+               self printWithClosureAnalysisReceiver: receiver ifNilReceiver ifNilValue on: aStream indent: level.<br>
+               ^ self printWithClosureAnalysisKeywords: #ifNotNil:<br>
-       self printWithClosureAnalysisReceiver: receiver ifNilReceiver on: aStream indent: level.<br>
- <br>
-       (arguments first isJust: NodeNil) ifTrue:<br>
-               [^self printWithClosureAnalysisKeywords: #ifNotNil:<br>
                                arguments: { arguments second }<br>
                                on: aStream indent: level].<br>
+                       <br>
+       (arguments second isJust: NodeNil) ifTrue: [<br>
+               self printWithClosureAnalysisReceiver: receiver ifNilReceiver on: aStream indent: level.<br>
+               ^ self printWithClosureAnalysisKeywords: #ifNil:<br>
-       (arguments second isJust: NodeNil) ifTrue:<br>
-               [^self printWithClosureAnalysisKeywords: #ifNil:<br>
                                arguments: { arguments first }<br>
                                on: aStream indent: level].<br>
+                       <br>
+       self printWithClosureAnalysisReceiver: receiver ifNilReceiver ifNilValue on: aStream indent: level.<br>
+       ^ self printWithClosureAnalysisKeywords: #ifNil:ifNotNil:<br>
-       ^self printWithClosureAnalysisKeywords: #ifNil:ifNotNil:<br>
                        arguments: arguments<br>
                        on: aStream indent: level!<br>
<br>
Item was added:<br>
+ ----- Method: ParseNode>>ifNilTemporary (in category 'private') -----<br>
+ ifNilTemporary<br>
+ <br>
+       ^ nil!<br>
<br>
Item was added:<br>
+ ----- Method: ParseNode>>ifNilValue (in category 'private') -----<br>
+ ifNilValue<br>
+ <br>
+       ^self!<br>
<br>
Item was changed:<br>
  ----- Method: Parser>>parseCue:noPattern:ifFail: (in category 'public access') -----<br>
  parseCue: aCue noPattern: noPattern ifFail: aBlock <br>
        "Answer a MethodNode for the argument, sourceStream, that is the root of<br>
         a parse tree. Parsing is done with respect to the CompilationCue to <br>
         resolve variables. Errors in parsing are reported to the cue's requestor; <br>
         otherwise aBlock is evaluated. The argument noPattern is a Boolean that is<br>
         true if the the sourceStream does not contain a method header (i.e., for DoIts)."<br>
<br>
        | methNode repeatNeeded myStream s p subSelection |<br>
        myStream := aCue sourceStream.<br>
        [repeatNeeded := false.<br>
         p := myStream position.<br>
         s := myStream upToEnd.<br>
         myStream position: p.<br>
+ <br>
+        doitFlag := noPattern.<br>
+        failBlock:= aBlock.<br>
<br>
         self encoder init: aCue notifying: self.<br>
         self init: myStream cue: aCue failBlock: [^ aBlock value].<br>
<br>
         subSelection := self interactive and: [cue requestor selectionInterval = (p + 1 to: p + s size)].<br>
<br>
-        doitFlag := noPattern.<br>
-        failBlock:= aBlock.<br>
         [methNode := self method: noPattern context: cue context] <br>
                on: ReparseAfterSourceEditing <br>
                do:     [ :ex |<br>
                        repeatNeeded := true.<br>
                        properties := nil. "Avoid accumulating pragmas and primitives Number"<br>
                        myStream := ex newSource <br>
                                ifNil: [subSelection<br>
                                                        ifTrue:<br>
                                                                [ReadStream<br>
                                                                        on: cue requestor text string<br>
                                                                        from: cue requestor selectionInterval first<br>
                                                                        to: cue requestor selectionInterval last]<br>
                                                        ifFalse:<br>
                                                                [ReadStream on: cue requestor text string]]<br>
                                ifNotNil: [:src | myStream := src readStream]].<br>
         repeatNeeded] whileTrue:<br>
                [encoder := self encoder class new].<br>
        methNode sourceText: s.<br>
        ^methNode<br>
  !<br>
<br>
<br>
</blockquote></div><br clear="all"><div><br></div>-- <br><div dir="ltr"><div dir="ltr"><div><span style="font-size:small;border-collapse:separate"><div>_,,,^..^,,,_<br></div><div>best, Eliot</div></span></div></div></div></div>
<br>
</blockquote></div>