Hello,
I need to do some Smalltalk code generation from Smalltalk. So I have implemented a library (although it may be seen as an embedded language). One important thing is that I need to compose generated code from other generated code.
A usage example:
cb := self commandBuilder m := cb methodNamed: #conditionMethod withFormalArgs: #() comment: 'Condition method'. m send: #ifTrue:ifFalse: to: (cb send: #isNil to: cb nil) with: (cb block: [:blk | blk return: (cb literal:'true')])
with: (cb block: [:blk | blk return: (cb literal: 'false')]).
The "pretty" printing of the generated method m would be:
conditionMethod "Condition method"
nil isNil ifTrue:[^true] ifFalse:[^false]
I want to do something like this, so I cant work on plain strings or do some preprocessing:
ASTVisitor>>visitAnd: node ^cb send: #and: to: (node exp1 accept: self) with: (cb block:[:blk | node exp2 accept: self])
And the question is: Is there a better/easier/trivial/standard way of doing this that I am missing?
Please don't doubt to criticize it, I want to learn :-) .
Thank you very much for your time
Marian
___________________________________________________________ 1GB gratis, Antivirus y Antispam Correo Yahoo!, el mejor correo web del mundo http://correo.yahoo.com.ar
Hi Marian,
have a look at my 'Functional Pattern Matching' package on SqueakMap. It does compose several pieces of Smalltalk code with user-defined conditions to one method using the AST of the Refactoring-Browser.
Cheers, Lukas
-- Lukas Renggli http://www.lukas-renggli.ch
Thanks, I'll take a look at it.
Cheers, Marian
Lukas Renggli wrote:
Hi Marian,
have a look at my 'Functional Pattern Matching' package on SqueakMap. It does compose several pieces of Smalltalk code with user-defined conditions to one method using the AST of the Refactoring-Browser.
Cheers, Lukas
-- Lukas Renggli http://www.lukas-renggli.ch
___________________________________________________________ 1GB gratis, Antivirus y Antispam Correo Yahoo!, el mejor correo web del mundo http://correo.yahoo.com.ar
Hi Mariano!
cb := self commandBuilder m := cb methodNamed: #conditionMethod withFormalArgs: #() comment: 'Condition method'. m send: #ifTrue:ifFalse: to: (cb send: #isNil to: cb nil) with: (cb block: [:blk | blk return: (cb literal:'true')])
with: (cb block: [:blk | blk return: (cb literal: 'false')]).
I was wondering in which case do you _really_ need such an approach?
Why not something like to be evaluated in a workspace:
Parser new parse: 'conditionMethod "Condition method" nil isNil ifTrue:[^true] ifFalse:[^false]' class: nil class
Cheers, Alexandre
Hi Alexandre, sorry for not answering before, I hadn't noticed the message.
The problem is that I have a code generation visitor traversing the AST. I need to generate different parts of the method from different methods of the visitor. I don't see how your approach may fit, that's why I need to implement it this way.
Thank you, Marian
Alexandre Bergel wrote:
Hi Mariano!
cb := self commandBuilder m := cb methodNamed: #conditionMethod withFormalArgs: #() comment: 'Condition method'. m send: #ifTrue:ifFalse: to: (cb send: #isNil to: cb nil) with: (cb block: [:blk | blk return: (cb literal:'true')])
with: (cb block: [:blk | blk return: (cb literal: 'false')]).
I was wondering in which case do you _really_ need such an approach?
Why not something like to be evaluated in a workspace:
Parser new parse: 'conditionMethod "Condition method" nil isNil ifTrue:[^true] ifFalse:[^false]' class: nil class
Cheers, Alexandre --_,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;: Alexandre Bergel http://www.iam.unibe.ch/~bergel ^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;.
Am 08.10.2005 um 05:47 schrieb Mariano Montone:
Hi Alexandre, sorry for not answering before, I hadn't noticed the message.
The problem is that I have a code generation visitor traversing the AST. I need to generate different parts of the method from different methods of the visitor. I don't see how your approach may fit, that's why I need to implement it this way.
Yes, then you don't want to deal with text, for sure. One way to do it is to visit your AST and build up a refactoring browser AST. The new compiler (see project NewCompiler on SqueakSource) then can generate bytecode from that. (Or you can use the standard Squeak AST for that, but this is far from fun to use...)
Another way to do it is to reuse just the back-end of the new compiler: Generate directly bytecode from your AST. This saved you from doing the conversion to the Squeak AST, thus it's faster and you are not bound to Smalltalk semantics, that is, you can encode controlflow with jumps directly as you like, thus generating quite nice code for your language.
The backend is IRBuilder: It's a kind of a "symbolic assembler" for Squeak Bytecode.
Here's a simple example:
iRMethod := IRBuilder new numRargs: 1; addTemps: #(self); "receiver and args declarations" pushLiteral: 1; returnTop; ir. aCompiledMethod := iRMethod compiledMethod.
So for your AST visitor, you make one IRBuilder, put it in an instVar of the visitor and then just call methods on it while traversing the tree. IRBuilder has support for symbolic jumps, a test showing this:
ir := IRBuilder new numRargs: 2; addTemps: #(self a z); "rcvr, arg, & extra temp (not used here)" pushTemp: #self; pushInstVar: 2; pushTemp: #a; send: #>; jumpAheadTo: #else if: false; pushLiteral: 'yes'; returnTop; jumpAheadTarget: #else; pushLiteral: 'no'; returnTop; ir. cm := ir compiledMethod. self assert: (cm isKindOf: CompiledMethod). self assert: (cm valueWithReceiver: self arguments: #(1)) = 'yes' . self assert: (cm valueWithReceiver: self arguments: #(3)) = 'no' . ^cm
There are some examples in the IRBuilderTest method (e.g. for how to do blocks).
Of course there are downsides: For a complete system you need to take care of debugging (a it more difficult, but it should be doable. The debugger just needs a mapping bytecode--
text that your compiler/decompiler need to
build up). And you need to keep in mind that the code generated will not be decompilable by the smalltalk decompiler, If you need a decompiler, then do your own that then will decompile directly to your AST.
One problem: It's not yet all to easy to get it in installed, the new compiler requires the latest 3.9a with AST and methodAnnotations (from http:// www.iam.unibe.ch/~denker/temp/CMAnnotations.zip). This will be fixed soon.
Marcus
Marcus, thank you for the long answer, with the examples and so on :-) . I'll take the bytecode generation approach into account for a future refactoring and optimization as I already have it working fine the other way.
Marian
Marcus Denker wrote:
Am 08.10.2005 um 05:47 schrieb Mariano Montone:
Hi Alexandre, sorry for not answering before, I hadn't noticed the message.
The problem is that I have a code generation visitor traversing the AST. I need to generate different parts of the method from different methods of the visitor. I don't see how your approach may fit, that's why I need to implement it this way.
Yes, then you don't want to deal with text, for sure. One way to do it is to visit your AST and build up a refactoring browser AST. The new compiler (see project NewCompiler on SqueakSource) then can generate bytecode from that. (Or you can use the standard Squeak AST for that, but this is far from fun to use...)
Another way to do it is to reuse just the back-end of the new compiler: Generate directly bytecode from your AST. This saved you from doing the conversion to the Squeak AST, thus it's faster and you are not bound to Smalltalk semantics, that is, you can encode controlflow with jumps directly as you like, thus generating quite nice code for your language.
The backend is IRBuilder: It's a kind of a "symbolic assembler" for Squeak Bytecode.
Here's a simple example:
iRMethod := IRBuilder new numRargs: 1; addTemps: #(self); "receiver and args declarations" pushLiteral: 1; returnTop; ir. aCompiledMethod := iRMethod compiledMethod.
So for your AST visitor, you make one IRBuilder, put it in an instVar of the visitor and then just call methods on it while traversing the tree. IRBuilder has support for symbolic jumps, a test showing this:
ir := IRBuilder new numRargs: 2; addTemps: #(self a z); "rcvr, arg, & extra temp (not
used here)" pushTemp: #self; pushInstVar: 2; pushTemp: #a; send: #>; jumpAheadTo: #else if: false; pushLiteral: 'yes'; returnTop; jumpAheadTarget: #else; pushLiteral: 'no'; returnTop; ir. cm := ir compiledMethod. self assert: (cm isKindOf: CompiledMethod). self assert: (cm valueWithReceiver: self arguments: #(1)) = 'yes' . self assert: (cm valueWithReceiver: self arguments: #(3)) = 'no' . ^cm
There are some examples in the IRBuilderTest method (e.g. for how to do blocks).
Of course there are downsides: For a complete system you need to take care of debugging (a it more difficult, but it should be doable. The debugger just needs a mapping bytecode-->text that your compiler/decompiler need to build up). And you need to keep in mind that the code generated will not be decompilable by the smalltalk decompiler, If you need a decompiler, then do your own that then will decompile directly to your AST.
One problem: It's not yet all to easy to get it in installed, the new compiler requires the latest 3.9a with AST and methodAnnotations (from http://www.iam.unibe.ch/~denker/temp/CMAnnotations.zip). This will be fixed soon.
Marcus
___________________________________________________________ 1GB gratis, Antivirus y Antispam Correo Yahoo!, el mejor correo web del mundo http://correo.yahoo.com.ar
squeak-dev@lists.squeakfoundation.org