<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>