Implementing the Generator¶
This document describes how to implement a code generator using the high-level DirectedCodegen interface.
DirectedCodegen¶
Smithy code generators typically all follow the same patterns. In fact,
the layout of existing code generators is so similar that a kind of
"golden-path" codegen architecture was designed called
directed codegen. The DirectedCodegen interface and CodegenDirector
class provide a kind of guided template for building a code generator.
DirectedCodegen brings together all the opinionated abstractions for
implementing a Smithy code generator.
SymbolandSymbolProviderclasses used to map shapes to code and decouple this logic from templates. (see Decoupling Codegen with Symbols)A language-specific
SymbolWritersubclass used to generate code using a simple template engine. (see Generating Code)A
SmithyIntegrationsubtype used to provide extension points to the generator. (see Making Codegen Pluggable)Easy to find helper methods for getting information from the model. (see Using the Semantic Model)
Pre-defined "directives" that tell you the kinds of shape and trait combinations that need to be code generated.
Implementing DirectedCodegen¶
The methods of DirectedCodegen break down the process of building up
and running a generator into specific methods.
public interface DirectedCodegen<C extends CodegenContext<S, ?, I>, S, I extends SmithyIntegration<S, ?, C>> {
SymbolProvider createSymbolProvider(CreateSymbolProviderDirective<S> directive);
C createContext(CreateContextDirective<S, I> directive);
void generateService(GenerateServiceDirective<C, S> directive);
default void generateResource(GenerateResourceDirective<C, S> directive) {}
void generateStructure(GenerateStructureDirective<C, S> directive);
void generateError(GenerateErrorDirective<C, S> directive);
void generateUnion(GenerateUnionDirective<C, S> directive);
void generateEnumShape(GenerateEnumDirective<C, S> directive);
void generateIntEnumShape(GenerateIntEnumDirective<C, S> directive);
default void customizeBeforeShapeGeneration(CustomizeDirective<C, S> directive) {}
default void customizeBeforeIntegrations(CustomizeDirective<C, S> directive) {}
default void customizeAfterIntegrations(CustomizeDirective<C, S> directive) {}
}
The source code for DirectedCodegen can be found on GitHub.
DirectedCodegen prerequisites¶
DirectedCodegen has a few prerequisites before it can be implemented.
A
SymbolProviderimplementation used to map Smithy shapes to Symbols (typeS). Mapping Smithy Shapes to Your Language provides guidance on how shapes should map to a programming language, and Decoupling Codegen with Symbols describes how Symbols are used to perform the actual mapping.A specific implementation of
CodegenContext(typeC). This object provides access to codegen settings, abstractions for writing files, and abstractions for creating code writers. This context object has its own prerequisites:A settings object for the code generator. This context object contains the codegen settings passed to your generator through
smithy-build.jsonplugins (see Configuring the Generator).A subclass of
SymbolWriterused to generate code for the target language.
A
SmithyIntegrationimplementation used to make the generator extensible (typeI).
Running DirectedCodegen using a CodegenDirector¶
A CodegenDirector is used in concert with a DirectedCodegen
implementation to build up the context needed to run the generator and
call methods in the right order. CodegenDirector is typically called
in a Smithy-Build plugin using the data provided by
software.amazon.smithy.build.PluginContext.
@Override
public void execute(PluginContext context) {
CodegenDirector<MylangWriter,
MylangIntegration,
MylangContext,
MylangSettings> runner = new CodegenDirector<>();
// Assuming MylangGenerator is an implementation of DirectedCodegen.
runner.directedCodegen(new MylangGenerator());
// Set the SmithyIntegration class to look for and apply using SPI.
runner.integrationClass(TestIntegration.class);
// Set the FileManifest and Model from the plugin.
runner.fileManifest(context.getFileManifest());
runner.model(context.getModel());
// Create a MylangSettings object from the plugin settings.
MylangSettings settings = runner.settings(MylangSettings.class,
context.getSettings());
// Assuming service() returns the configured service shape ID.
runner.service(settings.service());
// Configure the director to perform some common model transforms.
runner.performDefaultCodegenTransforms();
runner.createDedicatedInputsAndOutputs();
runner.run();
}
After performing the above steps, CodegenDirector will:
Perform any requested model transformations
Automatically find implementations of your
SmithyIntegrationclass using Java SPI. These implementations are then used throughout the rest of code generation.Register the
CodeInterceptorsfrom eachSmithyIntegrationwith yourWriterDelegatorCall each
generate* method in a topologically sorted order (that is, things with no references to other shapes come before shapes that reference them)Call
DirectedCodegen#customizeBeforeIntegrationsRun the
customizemethod of eachSmithyIntegrationCall
DirectedCodegen#customizeAfterIntegrationsFlush any open
SymbolWriters in yourWriterDelegator.
Creating a settings class¶
A code generator uses a settings object to configure the generator in
Smithy-Build and during directed code generation. At a minimum, this
class should have a ShapeId for the service to generate.
public final class MylangSettings {
private ShapeId service;
public void service(ShapeId service) {
this.service = service;
}
public ShapeId service() {
return service;
}
}
See also
Configuring the Generator defines recommended settings
Creating a CodegenContext class¶
This object provides access to codegen settings, abstractions for
writing files, and abstractions for creating code writers. You should
create a specific implementation of CodegenContext for each
generator. This can be done using a Java record, POJO, builder, etc.
public record MylangContext (
Model model,
MylangSettings settings,
SymbolProvider symbolProvider,
FileManifest fileManifest,
WriterDelegator<MylangWriter> writerDelegator,
List<MylangIntegration> integrations,
ServiceShape service
) implements CodegenContext<MylangSettings, MylangWriter, MylangIntegration> {}
DirectedCodegen#createContext is responsible for creating a
CodegenContext. Ensure that the data provided by your CodegenContext
are available using the data available to CreateContextDirective.
Tips for using DirectedCodegen¶
Each directive object provided to the methods of a DirectedCodegen implementation provide all the context needed to perform that action.
In addition to context, directives often provide helper methods to get information out of the model or shape being generated.
If additional data is needed in a given directive, you can:
Add new getters to your
CodegenContextclass.Add state to your
DirectedCodegenclass to set the context data you need.