///|
/// A writer with the namespace that the first use of its path asked for. The
/// factory can return a writer with another namespace, so I keep this one.
priv struct RequestedWriter {
ns : Namespace
writer : CodeWriter
}
///|
/// 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.
pub struct WriterDelegator {
priv provider : SymbolProvider
priv factory : (OutputPath, Namespace) -> CodeWriter
priv render : (CodeWriter) -> String
// The writers by the text of their path.
priv writers : Map[String, RequestedWriter]
// The interceptors, in the order that they were added.
priv interceptors : Array[Interceptor]
// The dependencies that were added, and those of the writers that were flushed.
priv kept : Array[SymbolDependency]
}
///|
pub fn WriterDelegator::new(
provider : SymbolProvider,
factory~ : (OutputPath, Namespace) -> CodeWriter = (_, ns) => {
CodeWriter::new(ns~)
},
render~ : (CodeWriter) -> String = w => w.to_string(),
) -> WriterDelegator {
{ provider, factory, render, writers: Map([]), interceptors: [], kept: [], }
}
///|
pub fn WriterDelegator::provider(self : WriterDelegator) -> SymbolProvider {
self.provider
}
///|
/// 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.
pub fn WriterDelegator::add_interceptors(
self : WriterDelegator,
interceptors : Array[Interceptor],
) -> Unit {
for interceptor in interceptors {
self.interceptors.push(interceptor)
for _, entry in self.writers {
entry.writer.on_section(interceptor)
}
}
}
///|
/// 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.
pub fn WriterDelegator::use_file_writer(
self : WriterDelegator,
path : OutputPath,
ns : Namespace,
body : (CodeWriter) -> Unit raise CodegenError,
) -> Unit raise CodegenError {
let key = path.to_string()
let writer = match self.writers.get(key) {
Some(entry) => {
let first = entry.ns
if first != ns {
raise WriterConflict(
"I found two namespaces, `\{first.text}` and `\{ns.text}`, for output file `\{key}`.",
)
}
entry.writer.blank_line()
entry.writer
}
None => {
let writer = (self.factory)(path, ns)
for interceptor in self.interceptors {
writer.on_section(interceptor)
}
self.writers[key] = { ns, writer, }
writer
}
}
body(writer)
}
///|
/// 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.
pub fn WriterDelegator::use_shape_writer(
self : WriterDelegator,
shape : @smithy.Shape,
body : (CodeWriter) -> Unit raise CodegenError,
) -> Unit raise CodegenError {
let symbol = self.provider.to_symbol(shape)
guard symbol.definition is Some(path) else {
raise MissingDefinition(shape.id)
}
self.use_file_writer(path, symbol.ns, writer => {
writer.reserve_name(symbol.name)
body(writer)
})
}
///|
/// Record a dependency of the generated code.
pub fn WriterDelegator::add_dependency(
self : WriterDelegator,
dependency : SymbolDependency,
) -> Unit {
self.kept.push(dependency)
}
///|
/// The dependencies that were added and those of all writers, sorted and
/// without duplicates. Flushed writers keep their dependencies.
pub fn WriterDelegator::dependencies(
self : WriterDelegator,
) -> Array[SymbolDependency] {
let found = self.kept.copy()
for _, entry in self.writers {
found.append(entry.writer.dependencies())
}
found.sort()
let result = []
for dependency in found {
if result.last() is Some(last) && last == dependency {
continue
}
result.push(dependency)
}
result
}
///|
/// 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.
pub fn WriterDelegator::flush(self : WriterDelegator) -> Map[String, String] {
let keys = self.writers.keys().collect()
keys.sort_by(lexical)
let files = Map([])
for key in keys {
let entry = self.writers[key]
self.kept.append(entry.writer.dependencies())
files[key] = (self.render)(entry.writer)
}
self.writers.clear()
files
}