Sign in

    smithy-codegen

    Language-neutral Smithy code generation core

    smithy
    codegen
    moonbit
    Download zip
    Author
    Version
    0.7.0
    License
    Apache-2.0
    Last updated
    4 hours ago
    Downloads
    5

    #Smithy code generation core

    moonrockz/smithy-codegen is a language-neutral core for Smithy code generators. A generator for one target language builds on it. The module has no file access, no network access and no process access. A generator returns its files as a Map[String, String] from a path to a text, and the build runner writes them.

    The output is deterministic. No unordered iteration decides the order of the output, and the same model gives the same bytes. Text sorts are lexical.

    #Parts

    PartTypes and functionsPurpose
    SymbolsSymbol, SymbolName, Namespace, OutputPath, PackageName, SymbolDependency, SymbolReferenceName a generated item, its file and its dependencies. Each type has a checked parse constructor.
    Symbol providerSymbolProvider, ReservedWordsMap a shape to a Symbol and a member to a name. Decorators and a cache add behavior.
    Shape orderservice_closure, topological_orderList the shapes of a service and order them by their dependencies.
    Code writerCodeWriter, TemplateValue, Section, InterceptorWrite text with templates, indentation, imports and sections.
    Import policiesImportPolicy, ImportEntry, PackageImportSelect how $T writes a foreign symbol: by name, or qualified by its package.
    Contextual namesServiceNamesApply the service rename map to shape names.
    Writer delegatorWriterDelegatorKeep one writer for each output file and collect the files.
    IntegrationsIntegration[C], sort_integrationsAdd behavior to a generator: ordered hooks for the model, the provider and the writers.
    DirectorDirectedCodegen[C], CreateContext, run_codegenWalk the shapes of a service and call one function for each kind of shape.
    Build pluginPluginContext, build_pluginRead the build settings and adapt a generator to the build runner.

    The walks keep their own stacks and do not recurse. The closure and the topological order finish for a chain of 20,000 shapes.

    #Shape order

    topological_order places a shape after the shapes that it depends on. A cycle is placed as one unit, by its dependencies. The order inside a cycle is lexical. The result keeps member shapes. The director skips them.

    #Errors

    Each function that can fail raises CodegenError. The messages are in the first person and name the construct. For example, a missing service gives MissingService, and a bad template gives InvalidTemplate with the offset of the problem. build_plugin converts a CodegenError to a build error.

    #Templates

    CodeWriter::write renders a template and ends the line. write_inline renders a template and continues the line. \r\n in a template becomes \n.

    SyntaxMeaning
    $$A literal $.
    $L, $S, $T, $CThe next argument, with a formatter.
    $1LThe argument at position 1 (positions start at 1).
    $name:L, ${name:L}The value name from the context of the writer.
    ${name:C\|}Start each later line of the value with the leading white space of the current line of the output. It is not the column of the expression.
    ${?x} ... ${/x}Render the block when x is true.
    ${^x} ... ${/x}Render the block when x is false.
    ${#x} ... ${/x}Render the block for each item of the list or record x.
    ${#x as k, v}The same, with the names k and v for the key and the value.
    key, valueThe key and the value of the item in a loop.
    key.first, key.lastTrue for the first and for the last item in a loop.
    ${- ... -}Remove the white space before or after the tag.

    The formatters are:

    FormatterResult
    LA literal: text, a number or a Boolean as it is.
    SA string literal, with quotes and escapes.
    TA symbol. The writer adds an import when the symbol is in another namespace.
    CCode from a Call value, written by a nested writer.

    Add a formatter with put_formatter. Its character is an uppercase ASCII letter.

    More rules:

    • A block tag alone on a line leaves no line in the output.
    • write adds a newline, unless the template ends with a block tag that is alone on its line and has no newline after it. The text of the blocks then already ends its line, so write adds no blank line.
    • A loop over a record uses the insertion order of the record.
    • ${#x} over a missing name or over Null is an error.
    • A template that fails writes nothing and leaves the import tracker unchanged.
    • Two symbols with the same name from two namespaces in one file: the writer writes the name of the second one in full.
    • A name that the file uses for its own symbols blocks a foreign import of that name: the writer writes the foreign symbol in full and does not import it. The file's own names are the symbols of the writer namespace and the names that reserve_name reserves. use_shape_writer reserves the name of its shape.
    • Use push_state and pop_state to save and restore the indentation, the newline prefix, the context and the formatters.
    • A foreign import that a file makes first wins over a later local symbol with the same name that the file did not reserve. Reserve the names of the file's own symbols with reserve_name before you write.

    #Import policies

    CodeWriter::new(imports=...) selects how $T writes a foreign symbol and what it records. All writers of one file share the policy.

    PolicyText of a foreign symbolRecordLanguage
    ImportPolicy::by_name(), the defaultThe name, or the local name of its referenceimports(): one ImportEntry for each namePython
    ImportPolicy::by_package(qualify~, qualifier~, locals_block_qualifiers~)qualify(qualifier, symbol), such as rpc.Decimal or @rpc.Decimalpackage_imports(): one PackageImport for each packageGo, MoonBit

    With a package policy, the namespace of a symbol is its package, such as the import path example/payments/rpc. A symbol of the writer namespace or of the root namespace has its bare name. qualifier gives the default qualifier of a package, and the local name of a reference is an explicit qualifier. The first use of a package fixes its qualifier. When another package of the file has the qualifier, I add the first free number from 2, in the order of use: rpc2, rpc3. With locals_block_qualifiers=true, the file's own names also block a qualifier. Go needs this, because a package name and a local name share one scope. MoonBit does not, because @rpc cannot be a local name.

    ///|
    test {
    let last_segment = (ns : @codegen.Namespace) => {
    let parts = ns.text.split("/").map(part => part.to_owned()).collect()
    @codegen.SymbolName::parse(parts[parts.length() - 1]).unwrap()
    }
    let writer = @codegen.CodeWriter::new(
    ns=@codegen.Namespace::parse("example/pos", separator="/").unwrap(),
    imports=@codegen.ImportPolicy::by_package(
    qualify=(qualifier, symbol) => {
    "@\{qualifier.to_string()}.\{symbol.name.to_string()}"
    },
    qualifier=last_segment,
    locals_block_qualifiers=false,
    ),
    )
    let decimal = @codegen.Symbol::new(
    @codegen.SymbolName::parse("Decimal").unwrap(),
    ns=@codegen.Namespace::parse("example/pos/rpc", separator="/").unwrap(),
    )
    writer.write("let amount : $T = $T::zero()", args=[
    @codegen.Sym(decimal),
    @codegen.Sym(decimal),
    ])
    assert_eq(
    writer.to_string(),
    "let amount : @rpc.Decimal = @rpc.Decimal::zero()\n",
    )
    assert_eq(
    writer.package_imports().map(entry => entry.qualifier.to_string()),
    ["rpc"],
    )
    }

    #Contextual names

    A service rename map gives a shape another name in the context of the service. ServiceNames::new(model, service) reads the map of one service, and ServiceNames::for_services(model, services) merges the maps of several services, such as the two services of a protocol. shape_name(id) gives the rename, else the name of the shape ID. A member keeps its member name. A generator applies its own case rules to the result. for_services raises InvalidSettings when the services give one shape two names, or when two shapes outside the prelude get the same name without regard to case.

    #Sections and interceptors

    inject_section opens and closes a named section in one call, and the section is empty. push_section and pop_section write a body between them. An Interceptor for the section name changes the body:

    • Interceptor::prepender writes before the body.
    • Interceptor::appender writes after the body.
    • Interceptor::new replaces the body. It receives the previous text.

    Interceptors run in the order of their registration. A section that opens in the middle of a line continues that line. A section with non-empty text, after the interceptors, ends the outer line. An interceptor that raises an error ends the section too: the writer drops its text and does not write it.

    #Example

    The example writes a structure. $T adds an import for a symbol from another namespace. An interceptor adds a line to the section header.

    ///|
    test {
    let item = @codegen.Symbol::new(
    @codegen.SymbolName::parse("Item").unwrap(),
    ns=@codegen.Namespace::parse("example.items", separator=".").unwrap(),
    )
    let order = @codegen.Symbol::new(
    @codegen.SymbolName::parse("Order").unwrap(),
    ns=@codegen.Namespace::parse("example.orders", separator=".").unwrap(),
    )
    let writer = @codegen.CodeWriter::new(ns=order.ns)
    writer.on_section(
    @codegen.Interceptor::appender("header", (w, _) => w.write("// generated")),
    )
    writer.inject_section(@codegen.Section::new("header"))
    writer.put_context("members", @codegen.Record({ "item": @codegen.Sym(item) }))
    writer.write(
    "structure $T {\n${#members}\n ${key:L}: ${value:T}\n${/members}\n}",
    args=[@codegen.Sym(order)],
    )
    assert_eq(
    writer.to_string(),
    "// generated\nstructure Order {\n item: Item\n}\n",
    )
    assert_eq(writer.imports().map(entry => entry.name.to_string()), ["Item"])
    }

    #Build plugin

    build_plugin adapts a function from PluginContext to the plugin type of moonrockz/smithy-build. The settings of a plugin start with service, the shape ID of the service. PluginContext::check_settings fails on a key that the generator does not know.

    The subpackage moonrockz/smithy-codegen/fixture is a toy generator that writes an outline of each shape. It tests the module end to end. The Smithy CLI registers it as the plugin codegenFixture. Its settings are service (required) and header (optional text for the first line of each file).

    { "version": "1.0", "sources": ["model"], "plugins": { "codegenFixture": { "service": "example.orders#Orders", "header": "generated" } } }

    smithy build --config smithy-build.json

    The plugin writes one file for each shape, for example example/orders/Order.outline. It also writes dependencies.txt when a symbol needs a package.

    CodegenError

    pub(all) suberror CodegenError {
    InvalidSettings(String)
    MissingService(
    ShapeId
    )
    NotAService(
    ShapeId
    )
    InvalidTemplate(String)
    WriterConflict(String)
    MissingDefinition(
    ShapeId
    )
    DuplicateIntegration(String)
    IntegrationCycle(Array[String])
    StateError(String)
    } derive(
    Debug
    )

    An error that the code generation core raises.

    CodegenError::message

    fn CodegenError::message(self : CodegenError) -> String

    The first-person message of an error. It names the construct.

    CodeWriter

    pub struct CodeWriter {
    // private fields
    }

    A writer of generated code. It keeps the output, the indentation, a stack of states (indentation, newline prefix, context and formatters) and the formatters.

    CodeWriter::add_dependency

    fn CodeWriter::add_dependency(self : CodeWriter, dependency : SymbolDependency) -> Unit

    Record a dependency of the generated file.

    CodeWriter::add_import

    fn CodeWriter::add_import(self : CodeWriter, symbol : Symbol, local_name? : SymbolName?) -> Unit

    Record the import and the dependencies of a symbol, and of its references, with the rules of $T, and write nothing.

    CodeWriter::blank_line

    fn CodeWriter::blank_line(self : CodeWriter) -> Unit

    Write an empty line.

    CodeWriter::child

    fn CodeWriter::child(self : CodeWriter) -> CodeWriter

    A new writer for a nested piece of code. It has the namespace and the import tracker of this writer, and a copy of its context and formatters. Its indentation and newline prefix are empty.

    CodeWriter::dedent

    fn CodeWriter::dedent(self : CodeWriter, levels? : Int) -> Unit raise CodegenError

    Remove levels of indentation. It raises StateError below zero.

    CodeWriter::dependencies

    fn CodeWriter::dependencies(self : CodeWriter) -> Array[SymbolDependency]

    The dependencies that the writers of this file recorded, sorted and without duplicates.

    CodeWriter::get_context

    fn CodeWriter::get_context(self : CodeWriter, name : String) -> TemplateValue?

    CodeWriter::imports

    fn CodeWriter::imports(self : CodeWriter) -> Array[ImportEntry]

    The imports that the writers of this file recorded, sorted and without duplicates.

    CodeWriter::indent

    fn CodeWriter::indent(self : CodeWriter, levels? : Int) -> Unit

    CodeWriter::inject_section

    fn CodeWriter::inject_section(self : CodeWriter, section : Section) -> Unit raise CodegenError

    Start a section and end it at once, so the interceptors can add text.

    CodeWriter::new

    fn CodeWriter::new(ns? : Namespace, indent_text? : String, string_literal? : (String) -> String, imports? : ImportPolicy) -> CodeWriter

    A writer for the namespace ns. imports selects how $T writes a foreign symbol; the default imports by name.

    CodeWriter::ns

    fn CodeWriter::ns(self : CodeWriter) -> Namespace

    CodeWriter::on_section

    fn CodeWriter::on_section(self : CodeWriter, interceptor : Interceptor) -> Unit

    Add an interceptor. It applies to the sections that this writer and its child writers end. Interceptors run in the order that they were added.

    CodeWriter::open_block

    fn CodeWriter::open_block(self : CodeWriter, open : String, close : String, args? : Array[TemplateValue], body : (CodeWriter) -> Unit raise CodegenError) -> Unit raise CodegenError

    Write the opening line, indent, run the body, dedent and write the closing line.

    CodeWriter::package_imports

    fn CodeWriter::package_imports(self : CodeWriter) -> Array[PackageImport]

    The packages that the writers of this file imported with a package policy, sorted by namespace. The list is empty with the name policy.

    CodeWriter::pop_section

    fn CodeWriter::pop_section(self : CodeWriter) -> Unit raise CodegenError

    End the section and restore the state. The interceptors of the section change its text in the order that they were added. Then I write the text with the indentation of the enclosing writer. I write nothing when the text is empty. A non-empty text ends the outer line, also when the section opened in the middle of a line. It raises StateError when no section is open, or when a push_state inside the section is still open. When an interceptor raises an error, the error passes through and the section is already ended: its text and its state are gone, and the writer does not write the section.

    CodeWriter::pop_state

    fn CodeWriter::pop_state(self : CodeWriter) -> Unit raise CodegenError

    Restore the state of the last push_state. It raises StateError when there is no push_state to undo.

    CodeWriter::push_section

    fn CodeWriter::push_section(self : CodeWriter, section : Section) -> Unit

    Start a section. It saves the state, as push_state does, with no indentation and no newline prefix. Until pop_section, the writer captures the text that it writes.

    CodeWriter::push_state

    fn CodeWriter::push_state(self : CodeWriter) -> Unit

    Save a copy of the indentation, the newline prefix, the context and the formatters. pop_state restores them.

    CodeWriter::put_context

    fn CodeWriter::put_context(self : CodeWriter, name : String, value : TemplateValue) -> Unit

    CodeWriter::put_formatter

    fn CodeWriter::put_formatter(self : CodeWriter, character : Char, formatter : (CodeWriter, TemplateValue) -> String raise CodegenError) -> Unit

    Set the formatter for a character. A template can use only the formatters whose character is an uppercase ASCII letter.

    CodeWriter::reserve_name

    fn CodeWriter::reserve_name(self : CodeWriter, name : SymbolName) -> Unit

    Reserve a name for a symbol that the file defines, such as the shape that the file declares. Later, $T writes a foreign symbol with this name by its full name and does not import it. A reserved name does not change an import that the file made before.

    CodeWriter::set_newline_prefix

    fn CodeWriter::set_newline_prefix(self : CodeWriter, prefix : String) -> Unit

    CodeWriter::to_string

    fn CodeWriter::to_string(self : CodeWriter) -> String

    The text that I wrote. I trim the trailing whitespace of each line and end with exactly one newline. I give the empty string when nothing is there.

    CodeWriter::write

    fn CodeWriter::write(self : CodeWriter, template : String, args? : Array[TemplateValue]) -> Unit raise CodegenError

    Write a template and end the line. The template can have several lines. When the template ends with a block tag that is alone on its line, the rendered text already ends its line, so I add no newline.

    CodeWriter::write_inline

    fn CodeWriter::write_inline(self : CodeWriter, template : String, args? : Array[TemplateValue]) -> Unit raise CodegenError

    Write a template and continue the current line. The indentation applies only at the start of a line.

    CreateContext

    pub struct CreateContext {
    model :
    Model

    plugin : PluginContext
    service :
    Shape

    provider : SymbolProvider
    integrations : Array[String]
    }

    What the director gives to create_context: the original plugin context, the model and the service after the integrations changed the model, the symbol provider and the names of the integrations in run order.

    DependencyKind

    pub(all) enum DependencyKind {
    Runtime
    Test
    Build
    } derive(Compare, Eq, Hash,
    Debug
    )

    DirectedCodegen

    pub struct DirectedCodegen[C] {
    // private fields
    }

    The steps of a generator. C is the context type that create_context makes. The director calls one generate_* step for each kind of shape.

    DirectedCodegen::new

    fn[C] DirectedCodegen::new(create_symbol_provider~ : (
    Model
    , PluginContext) -> SymbolProvider, create_context~ : (CreateContext) -> C, delegator~ : (C) -> WriterDelegator, generate_service~ : (C,
    Shape
    ) -> Unit raise CodegenError, generate_structure~ : (C,
    Shape
    ) -> Unit raise CodegenError, generate_error~ : (C,
    Shape
    ) -> Unit raise CodegenError, generate_union~ : (C,
    Shape
    ) -> Unit raise CodegenError, generate_enum~ : (C,
    Shape
    ) -> Unit raise CodegenError, generate_int_enum~ : (C,
    Shape
    ) -> Unit raise CodegenError, generate_operation? : (C,
    Shape
    ) -> Unit raise CodegenError, generate_resource? : (C,
    Shape
    ) -> Unit raise CodegenError, generate_list? : (C,
    Shape
    ) -> Unit raise CodegenError, generate_map? : (C,
    Shape
    ) -> Unit raise CodegenError, customize_before_integrations? : (C) -> Unit raise CodegenError, customize_after_integrations? : (C) -> Unit raise CodegenError) -> DirectedCodegen[C]

    Make a generator from its steps. The steps for operations, resources, lists and maps, and the two customize steps, do nothing by default.

    ImportEntry

    pub(all) struct ImportEntry {
    ns : Namespace
    name : SymbolName
    local_name : SymbolName?
    } derive(Compare, Eq, Hash,
    Debug
    )

    An import that a generated file needs. The local_name is the name that the file uses for the symbol, when it is not the name of the symbol.

    ImportPolicy

    pub struct ImportPolicy {
    // private fields
    }

    How $T writes a foreign symbol and what it records as the import.

    ImportPolicy::by_name

    fn ImportPolicy::by_name() -> ImportPolicy

    Import each foreign symbol by its name and write the bare name, or the local name of its reference. This suits Python. It is the default policy.

    ImportPolicy::by_package

    fn ImportPolicy::by_package(qualify~ : (SymbolName, Symbol) -> String, qualifier~ : (Namespace) -> SymbolName, locals_block_qualifiers~ : Bool) -> ImportPolicy

    Import the package of each foreign symbol, and write the symbol with qualify(qualifier, symbol), such as rpc.Decimal for Go or @rpc.Decimal for MoonBit. The namespace of a symbol is its package. qualifier gives the default qualifier of a package; the local name of a reference is an explicit qualifier. The first use of a package fixes its qualifier. When another package has the qualifier, or locals_block_qualifiers is true and the file uses the name for its own symbol, I add the first free number from 2: rpc2, rpc3.

    Integration

    pub struct Integration[C] {
    name : String
    run_before : Array[String]
    run_after : Array[String]
    priority : Int
    preprocess_model : (
    Model
    , PluginContext) ->
    Model
    raise CodegenError
    decorate_symbol_provider : (SymbolProvider, PluginContext) -> SymbolProvider
    interceptors : (C) -> Array[Interceptor]
    customize : (C) -> Unit raise CodegenError
    }

    A named extension of a generator. C is the context type of the director. The fields are read-only outside this package.

    Integration::new

    fn[C] Integration::new(name : String, run_before? : Array[String], run_after? : Array[String], priority? : Int, preprocess_model? : (
    Model
    , PluginContext) ->
    Model
    raise CodegenError, decorate_symbol_provider? : (SymbolProvider, PluginContext) -> SymbolProvider, interceptors? : (C) -> Array[Interceptor], customize? : (C) -> Unit raise CodegenError) -> Integration[C]

    Interceptor

    pub struct Interceptor {
    // private fields
    }

    A function that changes the text of the sections with one name. The function gets a child writer, the section and the previous text. The text of the child writer becomes the new text of the section.

    Interceptor::appender

    fn Interceptor::appender(section_name : String, run : (CodeWriter, Section) -> Unit raise CodegenError) -> Interceptor

    An interceptor that keeps the previous text and writes after it.

    Interceptor::new

    fn Interceptor::new(section_name : String, run : (CodeWriter, Section, String) -> Unit raise CodegenError) -> Interceptor

    Interceptor::prepender

    fn Interceptor::prepender(section_name : String, run : (CodeWriter, Section) -> Unit raise CodegenError) -> Interceptor

    An interceptor that keeps the previous text and writes before it.

    Interceptor::section_name

    fn Interceptor::section_name(self : Interceptor) -> String

    The name of the sections that the interceptor changes.

    Namespace

    pub struct Namespace {
    text : String
    separator : String
    } derive(Eq, Hash,
    Debug
    )

    A namespace of a symbol, with the separator that joins it to the name. The root namespace has empty text and an empty separator.

    Namespace::is_root

    fn Namespace::is_root(self : Namespace) -> Bool

    Namespace::parse

    fn Namespace::parse(text : String, separator~ : String) -> Namespace?

    Parse a namespace. Empty text is not valid: use Namespace::root.

    Namespace::root

    fn Namespace::root() -> Namespace

    OutputPath

    pub struct OutputPath(String) derive(Eq, Hash,
    Debug
    )

    A relative output path with / as the only separator.

    OutputPath::parse

    fn OutputPath::parse(text : String) -> OutputPath?

    Parse an output path. I reject empty text, a leading /, empty segments, . and .. segments, \ and a drive prefix such as C:.

    OutputPath::to_string

    fn OutputPath::to_string(self : OutputPath) -> String

    PackageImport

    pub(all) struct PackageImport {
    ns : Namespace
    qualifier : SymbolName
    explicit : Bool
    } derive(Eq, Hash,
    Debug
    )

    A package that a generated file imports, for a language that imports packages: Go import qualifier "path" or MoonBit "path" @qualifier in moon.pkg. explicit is true when a symbol reference asked for the qualifier.

    PackageName

    pub struct PackageName(String) derive(Eq, Hash,
    Debug
    )

    The name of a package that a generated file depends on.

    PackageName::parse

    fn PackageName::parse(text : String) -> PackageName?

    PackageName::to_string

    fn PackageName::to_string(self : PackageName) -> String

    PluginContext

    pub struct PluginContext {
    model :
    Model

    settings : Map[String, Json]
    service :
    Shape

    }

    What a plugin knows when the build runner calls it: the model, the plugin settings and the service shape that the settings name.

    PluginContext::check_settings

    fn PluginContext::check_settings(self : PluginContext, allowed : Array[String]) -> Unit raise CodegenError

    Check that every setting name is in the list. The name service is always allowed. The error names the first unknown name in sorted order.

    PluginContext::closure

    The shapes that the service reaches, sorted by shape ID text.

    PluginContext::from_build

    fn PluginContext::from_build(model :
    Model
    , settings : Json) -> PluginContext raise CodegenError

    Read the plugin settings. The settings must be an object. The setting service must be the shape ID of a service shape in the model.

    PluginContext::setting_string

    fn PluginContext::setting_string(self : PluginContext, name : String) -> String? raise CodegenError

    The text of a setting. The result is None when the setting is absent.

    ReservedWords

    pub struct ReservedWords {
    words :
    Set
    [String]
    case_sensitive : Bool
    escape_with : (String) -> String
    }

    A set of words that a target language reserves, with the way to escape them.

    ReservedWords::escape

    fn ReservedWords::escape(self : ReservedWords, word : String) -> String

    The escaped word, or the word itself when it is not reserved.

    ReservedWords::is_reserved

    fn ReservedWords::is_reserved(self : ReservedWords, word : String) -> Bool

    ReservedWords::new

    fn ReservedWords::new(words : Array[String], case_sensitive? : Bool, escape? : (String) -> String) -> ReservedWords

    Section

    pub(all) struct Section {
    name : String
    data : Map[String, TemplateValue]
    }

    A named place in the generated code. A section can carry data for the interceptors that change its text.

    Section::new

    fn Section::new(name : String, data? : Map[String, TemplateValue]) -> Section

    ServiceNames

    pub struct ServiceNames {
    // private fields
    }

    The contextual names of shapes in one or more services. A service rename map gives a shape another name in the context of that service. A generator applies its own case rules to the result.

    ServiceNames::for_services

    The names in the context of several services, such as the two services of a protocol that one generated package holds. I merge the rename maps. I raise InvalidSettings when the services give one shape two names, or when two shapes outside the prelude get the same name without regard to case.

    ServiceNames::new

    The names in the context of one service. It raises MissingService or NotAService for a bad service ID.

    ServiceNames::shape_name

    fn ServiceNames::shape_name(self : ServiceNames, id :
    ShapeId
    ) -> String

    The name of a shape in the context of the services: the rename, else the name of the shape ID. A member keeps its member name.

    Symbol

    pub struct Symbol {
    name : SymbolName
    ns : Namespace
    definition : OutputPath?
    references : Array[SymbolReference]
    dependencies : Array[SymbolDependency]
    properties : Map[String, Json]
    } derive(Eq,
    Debug
    )

    A symbol: the target-language name of a shape, where it is defined and what it needs.

    Symbol::all_dependencies

    fn Symbol::all_dependencies(self : Symbol) -> Array[SymbolDependency]

    The dependencies of the symbol and of its references, sorted and without duplicates. The walk keeps its own stack, so deep reference chains do not overflow, and a set of the full names that it has seen, so a cycle or a diamond of references ends.

    Symbol::full_name

    fn Symbol::full_name(self : Symbol) -> String

    The namespace, the separator and the name. Only the name when the namespace is root.

    Symbol::new

    fn Symbol::new(name : SymbolName, ns? : Namespace, definition? : OutputPath?) -> Symbol

    Symbol::property

    fn Symbol::property(self : Symbol, key : String) -> Json?

    Symbol::with_dependency

    fn Symbol::with_dependency(self : Symbol, dependency : SymbolDependency) -> Symbol

    A copy of the symbol with one more dependency.

    Symbol::with_name

    fn Symbol::with_name(self : Symbol, name : SymbolName) -> Symbol

    A copy of the symbol with another name.

    Symbol::with_property

    fn Symbol::with_property(self : Symbol, key : String, value : Json) -> Symbol

    A copy of the symbol with a property set.

    Symbol::with_reference

    fn Symbol::with_reference(self : Symbol, reference : SymbolReference) -> Symbol

    A copy of the symbol with one more reference.

    SymbolDependency

    pub(all) struct SymbolDependency {
    package_name : PackageName
    version : String
    kind : DependencyKind
    } derive(Eq, Hash,
    Debug
    )

    SymbolName

    pub struct SymbolName(String) derive(Eq, Hash,
    Debug
    )

    The name of a symbol. It is not empty and has no whitespace.

    SymbolName::parse

    fn SymbolName::parse(text : String) -> SymbolName?

    SymbolName::to_string

    fn SymbolName::to_string(self : SymbolName) -> String

    SymbolProvider

    pub struct SymbolProvider {
    symbol_fn : (
    Shape
    ) -> Symbol raise CodegenError
    member_fn : (
    Shape
    ) -> String
    }

    Maps a shape to its symbol and to its member name in the target language.

    SymbolProvider::cached

    A provider that calls the base provider once for each shape ID. It does not keep errors: a failed call runs again.

    SymbolProvider::decorate

    A provider that passes each symbol of the base provider through f.

    SymbolProvider::new

    fn SymbolProvider::new(to_symbol : (
    Shape
    ) -> Symbol raise CodegenError, to_member_name? : (
    Shape
    ) -> String) -> SymbolProvider

    SymbolProvider::to_member_name

    fn SymbolProvider::to_member_name(self : SymbolProvider, shape :
    Shape
    ) -> String

    SymbolProvider::to_symbol

    SymbolProvider::with_reserved_words

    fn SymbolProvider::with_reserved_words(self : SymbolProvider, symbols~ : ReservedWords, members~ : ReservedWords) -> SymbolProvider

    A provider that escapes symbol names with symbols and member names with members. The two sets of words are separate. A symbol whose escaped name is not a valid SymbolName raises InvalidSettings.

    SymbolReference

    pub struct SymbolReference {
    symbol : Symbol
    local_name : SymbolName?
    } derive(Eq,
    Debug
    )

    A reference from one symbol to another, with an optional local name.

    SymbolReference::new

    fn SymbolReference::new(symbol : Symbol, local_name? : SymbolName?) -> SymbolReference

    TemplateValue

    pub(all) enum TemplateValue {
    Null
    Bool(Bool)
    Int(Int)
    Text(String)
    Sym(Symbol)
    Ref(SymbolReference)
    Call((CodeWriter) -> Unit raise CodegenError)
    List(Array[TemplateValue])
    Record(Map[String, TemplateValue])
    }

    A value that a template can use: an argument or a context value.

    TemplateValue::is_truthy

    fn TemplateValue::is_truthy(self : TemplateValue) -> Bool

    False for Null, Bool(false), empty text, an empty list and an empty record. True for every other value, also for Int(0).

    WriterDelegator

    pub struct WriterDelegator {
    // private fields
    }

    Gives one writer to each output path. A generator asks for the writer of a file or of a shape, and the delegator makes sure that one file has one writer with one namespace.

    WriterDelegator::add_dependency

    fn WriterDelegator::add_dependency(self : WriterDelegator, dependency : SymbolDependency) -> Unit

    Record a dependency of the generated code.

    WriterDelegator::add_interceptors

    fn WriterDelegator::add_interceptors(self : WriterDelegator, interceptors : Array[Interceptor]) -> Unit

    Add interceptors to the writers that exist and to the writers that the delegator makes later. A writer gets them in the order that they were added.

    WriterDelegator::dependencies

    The dependencies that were added and those of all writers, sorted and without duplicates. Flushed writers keep their dependencies.

    WriterDelegator::flush

    fn WriterDelegator::flush(self : WriterDelegator) -> Map[String, String]

    Render every writer, with the path text as the key and the keys in sorted order. The delegator then has no writers, so a second call gives an empty map.

    WriterDelegator::new

    fn WriterDelegator::new(provider : SymbolProvider, factory? : (OutputPath, Namespace) -> CodeWriter, render? : (CodeWriter) -> String) -> WriterDelegator

    WriterDelegator::provider

    WriterDelegator::use_file_writer

    fn WriterDelegator::use_file_writer(self : WriterDelegator, path : OutputPath, ns : Namespace, body : (CodeWriter) -> Unit raise CodegenError) -> Unit raise CodegenError

    Run body with the writer of the path. The first use of a path makes the writer. A later use writes a blank line first. It raises WriterConflict when the path has a writer with another namespace. An error of body passes through, and the writer stays.

    WriterDelegator::use_shape_writer

    fn WriterDelegator::use_shape_writer(self : WriterDelegator, shape :
    Shape
    , body : (CodeWriter) -> Unit raise CodegenError) -> Unit raise CodegenError

    Run body with the writer of the file that defines the shape. The path and the namespace come from the symbol of the shape. The writer reserves the name of the symbol, so $T writes a foreign symbol with that name by its full name. It raises MissingDefinition when the symbol has no definition file.

    build_plugin

    fn build_plugin(run : (PluginContext) -> Map[String, String] raise CodegenError) -> ((
    Model
    , Json) -> Map[String, String] raise
    BuildError
    )

    Make the function that the build runner registers as a plugin. The function builds the plugin context and calls run. A code generation error becomes an invalid build configuration error with the message of the error.

    json_string_literal

    fn json_string_literal(text : String) -> String

    The text as a JSON string literal. I escape ", \, newline, carriage return and tab. I write other code points below 0x20 as \u00XX. I keep all other characters as they are.

    run_codegen

    fn[C] run_codegen(plugin : PluginContext, director : DirectedCodegen[C], integrations? : Array[Integration[C]]) -> Map[String, String] raise CodegenError

    Run a generator on the service of the plugin context.

    1. Sort the integrations.
    2. Let each integration change the model, in order. Find the service in the new model. It raises MissingService when the service is gone, and NotAService when it is not a service any more.
    3. Make the symbol provider, let each integration decorate it, and cache it.
    4. Make the context.
    5. Add the interceptors of the integrations to the delegator.
    6. Generate each shape of the service closure in topological order. I skip prelude shapes, member shapes and simple shapes.
    7. Run customize_before_integrations, the customize step of each integration, and customize_after_integrations.
    8. Return the files of the delegator.

    service_closure

    The service and every shape that it reaches through members, operations, errors and resources. Traits are not followed. A shape that the model does not hold is skipped. The result is sorted by shape ID text. The walk uses an explicit stack.

    sort_integrations

    fn[C] sort_integrations(items : Array[Integration[C]]) -> Array[Integration[C]] raise CodegenError

    Put the integrations in run order. a.run_before with b puts a before b. a.run_after with b puts b before a. Names that no integration has are ignored. Among the integrations that can run, the highest priority goes first, then the smallest name. The sort uses no recursion.

    topological_order

    Order shapes so that a shape comes after the shapes that it refers to. Every input ID appears once. A container refers to its members, and a member refers to its target. When a member is not in the input, the container refers to the target of that member. Shapes in a cycle come after the other shapes that are ready at the same time, in name order. Ties go to the smaller shape ID text. The model gives no edges to a shape that it does not hold. No step recurses.