Hi Pierre, El mar., 17 nov. 2020 a las 5:04, hogoww (<[email protected]>) escribió:
> 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. > > This is something which I've been expecting and trying to find some time to do myself. Just to be clear, you mean something like: [ :arg1 | arg1 + 0 ] , [ :arg2 | arg2 + 7 ] which would give: [ :arg1 :arg2 | arg1 + 0. arg2 + 7 ] Did I understand well? Thanks for this report! Cheers, Hernán > 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. > On 16/11/2020 23:55, [email protected] wrote: > > 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? > >
