/PharoCodeGenerator

A toolkit to generate Pharo code.

Primary LanguageSmalltalkMIT LicenseMIT

PharoCodeGenerator

License Pharo version Pharo version Pharo version Pharo version Pharo version

A toolkit to generate Pharo code. See the demo-video presented at ESUG'19 here.

Install

Metacello new
	repository: 'github://juliendelplanque/PharoCodeGenerator/src';
	baseline: 'PharoCodeGenerator';
	load

How it works

How PharoCodeGenerator works

API overview

Consider the following code snippet:

ast := (PCGMethodNode selector: #answerToEverything)
	bodyBlock: [ :body |
		body << 42 asPCG returnIt ]

This code generates the following method:

answerToEverything
	<generated>
	^ 42

Here are explanations on the methods one can send to the object stored in ast variable:

  • ast realAst : Builds the Pharo AST corresponding to description made in DSL
  • ast checkAst : Checks if the AST is ready for translation to Pharo AST. Raises an error if not.
  • ast sourceCode : Returns a String holding source code resulting from generation.
  • ast withGeneratedPragma: true|false : Adds or not <generated> pragma in method source code.
  • ast protocol: aString : Sets the protocol for the methods to be generated.
  • ast installOn: aBehaviour : Install the method on aBehaviour object (class, meta-class, trait, etc...).

DSL Examples

This section contains method from the system and the PCG code to generate them.

Generating Object>>#yourself

The following source code taken Object>>#yourself method:

yourself
  ^self

can be generated via the dsl:

(PCGMethodNode selector: #yourself)
	bodyBlock: [ :body | body << #self asPCGNode returnIt ];
	protocol: #accessing;
	yourself

Generating Collection>>#collect:

The following source code taken Collection>>#collect: method:

collect: aBlock
  | newCollection |
  newCollection := self species new.
  self do: [:each | newCollection add: (aBlock value: each) ].
  ^newCollection

can be generated via the dsl:

(PCGMethodNode selector: #collect: arguments: {#aBlock asPCGArgument})
	bodyBlock: [ :body | 
		body
			<<
				(#newCollection asPCGTemporary
					assign: ((#self asPCGNode receiveMessage: #species) receiveMessage: #new)).
		body
			<<
				(#self asPCGNode
					receiveMessage: #do:
					with:
						((PCGBlockNode arguments: {#each asPCGArgument})
							bodyBlock: [ :body1 | 
								body1
									<<
										(#newCollection asPCGTemporary
											receiveMessage: #add:
											with:
												(#aBlock asPCGArgument
													receiveMessage: #value:
													with: #each asPCGArgument)) ])).
		body << #newCollection asPCGTemporary returnIt ];
	protocol: #enumerating;
	yourself

Generating Integer>>#&

The following source code taken Integer>>#& method:

& aNumber
  ^self bitAnd: aNumber

can be generated via the dsl:

(PCGMethodNode selector: #& arguments: {#aNumber asPCGArgument})
	bodyBlock: [ :body | 
		body
			<<
				(#self asPCGNode receiveMessage: #bitAnd: with: #aNumber asPCGArgument)
					returnIt ];
	protocol: #'bit manipulation';
	yourself

Generating True>>#ifTrue:ifFalse:

The following source code taken True>>#ifTrue:ifFalse: method:

ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
  ^trueAlternativeBlock value

can be generated via the dsl:

(PCGMethodNode
	selector: #ifTrue:ifFalse:
	arguments:
	{#trueAlternativeBlock asPCGArgument.
#falseAlternativeBlock asPCGArgument})
	bodyBlock: [ :body | 
		body
			<< (#trueAlternativeBlock asPCGArgument receiveMessage: #value) returnIt ];
	protocol: #controlling;
	yourself

Template support

PCG provides support for templating.

template := (PCGMethodNode selector: #answerSelector asPCGTemplateParameter)
	bodyBlock: [ :body |
		body << #answer asPCGTemplateParameter returnIt ]

From a template, one can generate multiple methods using #substituteParametersWith: message:

The following substitution

(template substituteParametersWith: { 
	#answerSelector -> #answerToEverything.
	#answer -> 42 asPCG }) sourceCode.

generates

answerToEverything
	<generated>
	^ 42

This other substitution

(template substituteParametersWith: { 
	#answerSelector -> #answerToEverythingInParallelUniverse.
	#answer -> 43 asPCG }) sourceCode

generates

answerToEverythingInParallelUniverse
	<generated>
	^ 43

Generate PCG code from an existing method in the system

If you see a method in your project that can be used as a basis for code generation, PCG can help you.

Generate PCG code from existing method

Let us import the source code of Object>>#yourself as a PCG AST:

pcgAst := (Object >> #yourself) asPCGAST.

Then,

pcgAst sourceCode

generates:

yourself
	<generated>
	^ self

Now, one might need to retrieve the PCG script leading to the PCG AST that is able to generate Object>>#yourself. This can be achieve via #meta message.

Thus,

pcgAst meta sourceCode

generates

(PCGMethodNode selector: #yourself)
	bodyBlock: [ :body | body << #self asPCGNode returnIt ];
	protocol: #accessing;
	yourself

This mechanism is handy to generalize an exisiting methods in the system as a PCG template.