Hi Sean,
I'm aware of Julien Delplanque which has done something a bit
like this, or my own project.
Julien's has a good documentation.
Repo: https://github.com/juliendelplanque/PharoCodeGenerator
You also have my own project, which takes a different approach
that might be more what you want.
(I didn't document it as well though...):
https://github.com/hogoww/PlainPharoCode
Quickly, my way of doing things is to use blocks instead of
strings.
This ensures that the generated code is at least well formed.
This also enables code highlighting [...].
It then replaces variables using the lexical scope (except for
block args if i recall correctly, since shadowing is not a feature
of pharo)
```smalltalk
generateValidatorVisitOf: aClass
"add an empty behavior visit method"
| method body anInstance visitClass
errors | "IV of the visitor"
anInstance := aClass name asAnInstance.
visitClass := aClass name
asVisitClassSelector.
body := [ :aClass |
[ super visitClass:
{aClass} ]
on: AssertionFailure
do: [ errors := errors + 1 ]
].
method := visitClass
asMethodWithBody: body
withArguments: {(#aClass
-> anInstance )} asDictionary.
structureValidatorVisitorClass compile: method
asString classified: 'visiting'
```
This is a temp
variable, used as a selector, which will be replace when evaluating the
#asMethodWithBody:withArguments: message send.
The temp var visitClass is a string that
will describe the name of the method when sending the
#asMethodWithBody:withArguments: message.
This is the block that we want to generate.
The temp var errors has no value, which
means it already has the right name. this will not be
replaced.
The message send asMethodWithBody will do
the required replacements.
As said earlier , block arguments cannot be
replaced using the same method, as Pharo disabled shadowing.
We currently have to compile the string
version of the method. Although slow, this has not yet been an
issue
(I hope that color way of describing a method is readable :D)
This will for example give the folowing output:
```smalltalk
visitExpression: anExpression
[ super visitExpression: anExpression ]
on: AssertionFailure
do: [ errors := errors + 1 ]
```
I took a simple ish example, but we can do more advanced stuff,
like concatenating blocks.
You can find more example in the following project in which I use
PlainPharoCode exclusively to generate my code:
https://github.com/hogoww/C-AST.
(In the ASTCGenerator class)
(You will need a moose image).
I hope you find this interesting !
Have a nice day !
Pierre.
To generate code, I used to use templates like the following (contrived example):
```smalltalk
template := 'method1
^ {returnValue}'.
template format: { #returnValue -> 2 } asDictionary
```
This is *okay* for simple cases, but can get unwieldy, and doesn't benefit from e.g. (early) compiler warnings. It is also lacking when one wants to offer the behavior as a method and also as a script with no dependencies (maybe for CI).
To overcome some of these limitations, I started saving the code template as an actual method and transforming the AST, e.g.:
```smalltalk
"Convert baseline##: method"
methodTree := (self methodNamed: selector) parseTree.
methodTree selector: #baseline:.
methodTree pragmas at: 1 put: (RBPragmaNode selector: #baseline arguments: #()).
commonBlockBody := methodTree statements first arguments last body.
commonBlockBody statements
detect: [ :e | e selector = #repository: ]
ifFound: [ :repoSetter | commonBlockBody removeNode: repoSetter ].
"Compile baseline method"
baseline compile: methodTree newSource classified: 'baseline'
```
When I started doing this, I noticed two things:
1. There were idioms that were repeated over and over.
2. It still isn't super easy to turn a method into a script with no dependencies (e.g. self sends)
So, I did a spike to wrap a few common idioms for this use case. It works like this. Say you have a method like this:
```
SmallRemoteGitRepository>>#addRemote: urlString as: nameString
"Assumes repo is loaded"
| repo remote |
repo := IceRepository registry detect: [ :e | e name = self projectName ].
remote := IceGitRemote name: nameString url: urlString.
repo addRemote: remote.
remote fetch
```
And you want to also provide it as source code for a script with no dependencies. You can do:
```
SmallRemoteGitRepository >>#scriptToAddRemote: urlString as: nameString
| method transformer |
method := SmallRemoteGitRepository methodNamed: #addRemote:as:.
transformer := method peAST_Transformer
beScript;
addStatementFirst: #projectName , ' := ' , self projectName printString;
addStatementFirst: #nameString , ' := ' , nameString printString;
addStatementFirst: #urlString , ' := ' , urlString printString;
replaceNodeDetect: [ :e | e isMessage and: [ e selector = #projectName ] ]
withNode: (RBVariableNode named: #projectName).
^ transformer newSource
```
which would generate (for example, for a particular instance):
```
| repo remote projectName |
urlString := '[hidden email]'.
nameString := 'upstream'.
projectName := 'Magritte'.
repo := IceRepository registry detect: [ :e | e name = projectName ].
remote := IceGitRemote name: nameString url: urlString.
repo addRemote: remote.
remote fetch
```
So my questions are:
- Does something like this already exist?
- Are there better ways to solve this problem?
- Does this solution look promising/generally-helpful?
Free forum by Nabble | Edit this page |