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 := '[email protected]:magritte-metamodel/magritte.git'.`
`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?