Current section
Files
Jump to
Current section
Files
src/gleamgen/module.gleam
import glam/doc
import glance
import gleam/bool
import gleam/dict
import gleam/int
import gleam/list
import gleam/option.{type Option}
import gleam/result
import gleam/string
import gleamgen/expression.{type Expression}
import gleamgen/expression/constructor
import gleamgen/function
import gleamgen/import_
import gleamgen/internal/import_reference
import gleamgen/internal/render
import gleamgen/module/definition
import gleamgen/render as public_render
import gleamgen/source
import gleamgen/type_.{type Dynamic}
import gleamgen/type_/custom
pub opaque type ExternalModule {
ExternalModule(
module: Option(source.SourceMapped(glance.Module)),
definitions: List(ModuleDefinition),
)
}
pub opaque type Module {
Module(
definitions: List(ModuleDefinition),
imports: List(import_.ImportedModule),
external_module: Option(ExternalModule),
module_documentation_comments: List(String),
)
}
type PredefinedDefinition {
PredefinedImport(glance.Definition(glance.Import))
PredefinedCustomType(glance.Definition(glance.CustomType))
PredefinedTypeAlias(glance.Definition(glance.TypeAlias))
PredefinedConstant(glance.Definition(glance.Constant))
PredefinedFunction(glance.Definition(glance.Function))
}
pub type ModuleDefinition {
Definition(details: definition.Definition, value: Definable)
}
pub opaque type Definable {
Function(function.Function(Dynamic, Dynamic))
CustomTypeBuilder(custom.CustomTypeBuilder(Dynamic, Nil, Nil))
Constant(Expression(Dynamic))
TypeAlias(type_.GeneratedType(Dynamic))
Predefined(
ast: PredefinedDefinition,
text_before: String,
content: String,
source: source.Source,
)
}
pub fn from_source_map(module: source.SourceMapped(glance.Module)) -> Module {
let get_location = fn(definition: PredefinedDefinition) {
case definition {
PredefinedImport(def) -> def.definition.location
PredefinedConstant(def) -> def.definition.location
PredefinedCustomType(def) -> def.definition.location
PredefinedTypeAlias(def) -> def.definition.location
PredefinedFunction(def) -> def.definition.location
}
}
let #(module, _source) =
[
list.map(module.contents.imports, PredefinedImport),
list.map(module.contents.constants, PredefinedConstant),
list.map(module.contents.custom_types, PredefinedCustomType),
list.map(module.contents.type_aliases, PredefinedTypeAlias),
list.map(module.contents.functions, PredefinedFunction),
]
|> list.flatten()
|> list.sort(fn(first, second) {
int.compare(get_location(first).start, get_location(second).start)
})
|> source.fold(
module.source,
Module([], [], option.Some(ExternalModule(option.Some(module), [])), []),
handle_existing_definition,
get_location,
)
module
}
fn handle_existing_definition(
predefined_definition: PredefinedDefinition,
module: Module,
before_text: String,
definition_text: String,
source: source.Source,
) -> Module {
let add_definition = fn(name, publicity, attributes) {
let external_module =
module.external_module
|> option.unwrap(ExternalModule(option.None, []))
let is_public = case publicity {
glance.Public -> True
glance.Private -> False
}
let attributes =
list.filter_map(attributes, definition.attribute_from_glance)
let details =
definition.new(name)
|> definition.with_publicity(is_public)
|> definition.with_attributes(attributes)
|> definition.with_text_before(before_text)
|> definition.set_predefined(True)
let new_definition =
Definition(
details:,
value: Predefined(
predefined_definition,
before_text,
definition_text,
source,
),
)
Module(
..module,
external_module: option.Some(
ExternalModule(..external_module, definitions: [
new_definition,
..external_module.definitions
]),
),
)
}
case predefined_definition {
PredefinedImport(definition) -> {
let new_import = import_.convert_import(definition, before_text)
Module(..module, imports: [new_import, ..module.imports])
}
PredefinedConstant(definition) ->
add_definition(
definition.definition.name,
definition.definition.publicity,
definition.attributes,
)
PredefinedCustomType(definition) ->
add_definition(
definition.definition.name,
definition.definition.publicity,
definition.attributes,
)
PredefinedTypeAlias(definition) ->
add_definition(
definition.definition.name,
definition.definition.publicity,
definition.attributes,
)
PredefinedFunction(definition) ->
add_definition(
definition.definition.name,
definition.definition.publicity,
definition.attributes,
)
}
}
pub fn with_module_documentation_comments(
comments: List(String),
handler: fn() -> Module,
) -> Module {
let rest = handler()
Module(..rest, module_documentation_comments: comments)
}
pub fn with_constant(
details: definition.Definition,
value: Expression(t),
handler: fn(Expression(t)) -> Module,
) -> Module {
let rest = handler(expression.raw(definition.get_name(details)))
Module(..rest, definitions: [
Definition(details:, value: Constant(value |> expression.to_dynamic())),
..rest.definitions
])
}
pub fn with_import(
module: import_.ImportedModule,
handler: fn(import_.ImportReference) -> Module,
) -> Module {
let rest = handler(import_.import_to_reference(module))
Module(..rest, imports: [module, ..rest.imports])
}
pub fn with_dynamic_imports(
modules: List(import_.ImportedModule),
handler: fn(List(import_.ImportedModule)) -> Module,
) -> Module {
let rest = handler(modules)
Module(..rest, imports: list.append(list.reverse(modules), rest.imports))
}
pub type ReplacementConfig {
ReplacementInline
ReplacementUpdateDefinition(
fn(definition.Definition) -> definition.Definition,
)
}
pub fn replace_function(
function_name: String,
module: Module,
func: fn(Option(source.SourceMapped(glance.Function))) ->
function.Function(func_type, ret),
config: ReplacementConfig,
handler: fn(Module, Expression(func_type)) -> Module,
) -> Module {
let rest = handler(module, expression.raw(function_name))
case module.external_module {
option.Some(ExternalModule(definitions:, ..) as external_module) -> {
let definitions =
list.map(definitions, fn(definition) {
use <- bool.guard(
definition.get_name(definition.details) != function_name,
definition,
)
let #(details, value) = case definition.value {
Predefined(PredefinedFunction(f), _, _, source:) -> {
let details = case config {
ReplacementInline -> definition.details
ReplacementUpdateDefinition(update_fn) ->
update_fn(definition.details)
}
#(
definition.set_predefined(details, False),
Function(
func(
option.Some(source.SourceMapped(
contents: f.definition,
source:,
)),
)
|> function.to_dynamic(),
),
)
}
v -> #(definition.details, v)
}
Definition(details:, value:)
})
Module(
..module,
external_module: option.Some(
ExternalModule(..external_module, definitions:),
),
)
}
option.None -> {
Module(..rest, definitions: [
Definition(
definition.new(function_name),
value: Function(func(option.None) |> function.to_dynamic()),
),
..rest.definitions
])
}
}
}
pub fn with_function(
details: definition.Definition,
func: function.Function(func_type, ret),
handler: fn(Expression(func_type)) -> Module,
) -> Module {
let rest = handler(expression.raw(definition.get_name(details)))
Module(..rest, definitions: [
Definition(details:, value: Function(func |> function.to_dynamic())),
..rest.definitions
])
}
/// Define a custom type with one variant.
///
/// ```gleam
/// let cat =
/// custom.new()
/// |> custom.with_variant(fn(_) {
/// variant.new("Cat")
/// |> variant.with_argument(option.Some("name"), type_.string)
/// |> variant.with_argument(option.Some("age"), type_.int)
/// |> variant.with_argument(option.Some("has_catnip"), type_.bool)
/// })
///
/// use cat_type, cat_constructor <- module.with_custom_type1(
/// definition.new("Cat"),
/// cat,
/// )
/// ```
///
/// The type can be instantiated with [`custom.to_type`](type_/custom.html#to_type1).
/// The constructors can be used to create values of the type ([`constructor.to_expression`](expression/constructor.html#to_expression1))
/// or pattern match on values of the type ([`pattern.from_constructor`](pattern.html#from_constructor1)).
///
/// To define a custom type with a dynamic number of variants, see [`with_custom_type_dynamic`](#with_custom_type_dynamic).
pub fn with_custom_type1(
details: definition.Definition,
type_: custom.CustomTypeBuilder(repr, custom.Generics1(a), generics),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
) ->
Module,
) -> Module {
let assert [variant1] = type_.variants
let rest =
handler(
custom.new_custom_type(option.None, definition.get_name(details)),
constructor.new(option.None, variant1),
)
Module(..rest, definitions: [
Definition(details:, value: CustomTypeBuilder(type_ |> custom.to_dynamic())),
..rest.definitions
])
}
/// Import a custom type with one variant.
///
/// ```gleam
/// use cat_module <- module.with_import(import_.new(["animal", "cat"]))
/// let cat =
/// custom.new()
/// |> custom.with_variant(fn(_) {
/// variant.new("Cat")
/// |> variant.with_argument(option.Some("name"), type_.string)
/// |> variant.with_argument(option.Some("age"), type_.int)
/// |> variant.with_argument(option.Some("has_catnip"), type_.bool)
/// })
///
/// use cat_type, cat_constructor <- module.with_imported_custom_type1(
/// cat_module,
/// "Cat"
/// cat,
/// )
/// ```
///
/// The type can be instantiated with [`custom.to_type`](type_/custom.html#to_type1).
/// The constructors can be used to create values of the type ([`constructor.to_expression`](expression/constructor.html#to_expression1))
/// or pattern match on values of the type ([`pattern.from_constructor`](pattern.html#from_constructor1)).
///
/// To define a custom type with a dynamic number of variants, see [`with_custom_type_dynamic`](#with_custom_type_dynamic).
pub fn with_imported_custom_type1(
module: import_.ImportReference,
name: String,
type_: custom.CustomTypeBuilder(repr, custom.Generics1(a), generics),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
) ->
Module,
) -> Module {
let assert [variant1] = type_.variants
handler(
custom.new_custom_type(option.Some(module), name),
constructor.new(option.Some(module), variant1),
)
}
// {{{
/// Define a custom type with two variants.
/// ```gleam
/// let animals =
/// custom.new()
/// |> custom.with_variant(fn(_) {
/// variant.new("Dog")
/// |> variant.with_argument(option.Some("bones"), type_.int)
/// })
/// |> custom.with_variant(fn(_) {
/// variant.new("Cat")
/// |> variant.with_argument(option.Some("name"), type_.string)
/// |> variant.with_argument(option.Some("has_catnip"), type_.bool)
/// })
///
/// use animal_type, dog_constructor, cat_constructor <- module.with_custom_type2(
/// definition.new("Animal") |> definition.with_publicity(True),
/// animals,
/// )
/// ```
/// The type can be instantiated with [`custom.to_type`](type_/custom.html#to_type1).
/// The constructors can be used to create values of the type ([`constructor.to_expression`](expression/constructor.html#to_expression1))
/// or pattern match on values of the type ([`pattern.from_constructor`](pattern.html#from_constructor1)).
///
/// To define a custom type with a dynamic number of variants, see [`with_custom_type_dynamic`](#with_custom_type_dynamic).
pub fn with_custom_type2(
details: definition.Definition,
type_: custom.CustomTypeBuilder(repr, custom.Generics2(a, b), generics),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
) ->
Module,
) -> Module {
let assert [variant2, variant1] = type_.variants
let rest =
handler(
custom.new_custom_type(option.None, definition.get_name(details)),
constructor.new(option.None, variant1),
constructor.new(option.None, variant2),
)
Module(..rest, definitions: [
Definition(details:, value: CustomTypeBuilder(type_ |> custom.to_dynamic())),
..rest.definitions
])
}
/// Import a custom type with two variants.
/// ```gleam
/// use option_module <- module.with_import(import_.new(["gleam", "option"]))
/// let option =
/// custom.new()
/// |> custom.with_generic("a")
/// |> custom.with_variant(fn(generics) {
/// let #(#(), a) = generics
/// variant.new("Some")
/// |> variant.with_argument(option.None, a)
/// })
/// |> custom.with_variant(fn(_generics) { variant.new("None") })
///
/// use option_type, some_constructor, none_constructor <- module.with_imported_custom_type2(
/// option_module,
/// "Option",
/// option,
/// )
/// ```
/// The type can be instantiated with [`custom.to_type`](type_/custom.html#to_type1).
/// The constructors can be used to create values of the type ([`constructor.to_expression`](expression/constructor.html#to_expression1))
/// or pattern match on values of the type ([`pattern.from_constructor`](pattern.html#from_constructor1)).
///
/// To import a custom type with a dynamic number of variants, see [`with_imported_custom_type_dynamic`](#with_imported_custom_type_dynamic).
pub fn with_imported_custom_type2(
module: import_.ImportReference,
name: String,
type_: custom.CustomTypeBuilder(repr, custom.Generics2(a, b), generics),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
) ->
Module,
) -> Module {
let assert [variant2, variant1] = type_.variants
handler(
custom.new_custom_type(option.Some(module), name),
constructor.new(option.Some(module), variant1),
constructor.new(option.Some(module), variant2),
)
}
/// Define a custom type with three variants.
/// See [`with_custom_type1`](#with_custom_type1).
pub fn with_custom_type3(
details: definition.Definition,
type_: custom.CustomTypeBuilder(repr, custom.Generics3(a, b, c), generics),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
) ->
Module,
) -> Module {
let assert [variant3, variant2, variant1] = type_.variants
let rest =
handler(
custom.new_custom_type(option.None, definition.get_name(details)),
constructor.new(option.None, variant1),
constructor.new(option.None, variant2),
constructor.new(option.None, variant3),
)
Module(..rest, definitions: [
Definition(details:, value: CustomTypeBuilder(type_ |> custom.to_dynamic())),
..rest.definitions
])
}
/// Import a custom type with three variants.
/// See [`with_imported_custom_type1`](#with_imported_custom_type1).
pub fn with_imported_custom_type3(
module: import_.ImportReference,
name: String,
type_: custom.CustomTypeBuilder(repr, custom.Generics3(a, b, c), generics),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
) ->
Module,
) -> Module {
let assert [variant3, variant2, variant1] = type_.variants
handler(
custom.new_custom_type(option.Some(module), name),
constructor.new(option.Some(module), variant1),
constructor.new(option.Some(module), variant2),
constructor.new(option.Some(module), variant3),
)
}
/// Define a custom type with four variants.
/// See [`with_custom_type1`](#with_custom_type1).
pub fn with_custom_type4(
details: definition.Definition,
type_: custom.CustomTypeBuilder(repr, custom.Generics4(a, b, c, d), generics),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
constructor.Constructor(repr, d, generics),
) ->
Module,
) -> Module {
let assert [variant4, variant3, variant2, variant1] = type_.variants
let rest =
handler(
custom.new_custom_type(option.None, definition.get_name(details)),
constructor.new(option.None, variant1),
constructor.new(option.None, variant2),
constructor.new(option.None, variant3),
constructor.new(option.None, variant4),
)
Module(..rest, definitions: [
Definition(details:, value: CustomTypeBuilder(type_ |> custom.to_dynamic())),
..rest.definitions
])
}
/// Import a custom type with four variants.
/// See [`with_imported_custom_type1`](#with_imported_custom_type1).
pub fn with_imported_custom_type4(
module: import_.ImportReference,
name: String,
type_: custom.CustomTypeBuilder(repr, custom.Generics4(a, b, c, d), generics),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
constructor.Constructor(repr, d, generics),
) ->
Module,
) -> Module {
let assert [variant4, variant3, variant2, variant1] = type_.variants
handler(
custom.new_custom_type(option.Some(module), name),
constructor.new(option.Some(module), variant1),
constructor.new(option.Some(module), variant2),
constructor.new(option.Some(module), variant3),
constructor.new(option.Some(module), variant4),
)
}
/// Define a custom type with five variants.
/// See [`with_custom_type1`](#with_custom_type1).
pub fn with_custom_type5(
details: definition.Definition,
type_: custom.CustomTypeBuilder(
repr,
custom.Generics5(a, b, c, d, e),
generics,
),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
constructor.Constructor(repr, d, generics),
constructor.Constructor(repr, e, generics),
) ->
Module,
) -> Module {
let assert [variant5, variant4, variant3, variant2, variant1] = type_.variants
let rest =
handler(
custom.new_custom_type(option.None, definition.get_name(details)),
constructor.new(option.None, variant1),
constructor.new(option.None, variant2),
constructor.new(option.None, variant3),
constructor.new(option.None, variant4),
constructor.new(option.None, variant5),
)
Module(..rest, definitions: [
Definition(details:, value: CustomTypeBuilder(type_ |> custom.to_dynamic())),
..rest.definitions
])
}
/// Import a custom type with five variants.
/// See [`with_imported_custom_type1`](#with_imported_custom_type1).
pub fn with_imported_custom_type5(
module: import_.ImportReference,
name: String,
type_: custom.CustomTypeBuilder(
repr,
custom.Generics5(a, b, c, d, e),
generics,
),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
constructor.Constructor(repr, d, generics),
constructor.Constructor(repr, e, generics),
) ->
Module,
) -> Module {
let assert [variant5, variant4, variant3, variant2, variant1] = type_.variants
handler(
custom.new_custom_type(option.Some(module), name),
constructor.new(option.Some(module), variant1),
constructor.new(option.Some(module), variant2),
constructor.new(option.Some(module), variant3),
constructor.new(option.Some(module), variant4),
constructor.new(option.Some(module), variant5),
)
}
/// Define a custom type with six variants.
/// See [`with_custom_type1`](#with_custom_type1).
pub fn with_custom_type6(
details: definition.Definition,
type_: custom.CustomTypeBuilder(
repr,
custom.Generics6(a, b, c, d, e, f),
generics,
),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
constructor.Constructor(repr, d, generics),
constructor.Constructor(repr, e, generics),
constructor.Constructor(repr, f, generics),
) ->
Module,
) -> Module {
let assert [variant6, variant5, variant4, variant3, variant2, variant1] =
type_.variants
let rest =
handler(
custom.new_custom_type(option.None, definition.get_name(details)),
constructor.new(option.None, variant1),
constructor.new(option.None, variant2),
constructor.new(option.None, variant3),
constructor.new(option.None, variant4),
constructor.new(option.None, variant5),
constructor.new(option.None, variant6),
)
Module(..rest, definitions: [
Definition(details:, value: CustomTypeBuilder(type_ |> custom.to_dynamic())),
..rest.definitions
])
}
/// Import a custom type with six variants.
/// See [`with_imported_custom_type1`](#with_imported_custom_type1).
pub fn with_imported_custom_type6(
module: import_.ImportReference,
name: String,
type_: custom.CustomTypeBuilder(
repr,
custom.Generics6(a, b, c, d, e, f),
generics,
),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
constructor.Constructor(repr, d, generics),
constructor.Constructor(repr, e, generics),
constructor.Constructor(repr, f, generics),
) ->
Module,
) -> Module {
let assert [variant6, variant5, variant4, variant3, variant2, variant1] =
type_.variants
handler(
custom.new_custom_type(option.Some(module), name),
constructor.new(option.Some(module), variant1),
constructor.new(option.Some(module), variant2),
constructor.new(option.Some(module), variant3),
constructor.new(option.Some(module), variant4),
constructor.new(option.Some(module), variant5),
constructor.new(option.Some(module), variant6),
)
}
/// Define a custom type with seven variants.
/// See [`with_custom_type1`](#with_custom_type1).
pub fn with_custom_type7(
details: definition.Definition,
type_: custom.CustomTypeBuilder(
repr,
custom.Generics7(a, b, c, d, e, f, g),
generics,
),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
constructor.Constructor(repr, d, generics),
constructor.Constructor(repr, e, generics),
constructor.Constructor(repr, f, generics),
constructor.Constructor(repr, g, generics),
) ->
Module,
) -> Module {
let assert [
variant7,
variant6,
variant5,
variant4,
variant3,
variant2,
variant1,
] = type_.variants
let rest =
handler(
custom.new_custom_type(option.None, definition.get_name(details)),
constructor.new(option.None, variant1),
constructor.new(option.None, variant2),
constructor.new(option.None, variant3),
constructor.new(option.None, variant4),
constructor.new(option.None, variant5),
constructor.new(option.None, variant6),
constructor.new(option.None, variant7),
)
Module(..rest, definitions: [
Definition(details:, value: CustomTypeBuilder(type_ |> custom.to_dynamic())),
..rest.definitions
])
}
/// Import a custom type with seven variants.
/// See [`with_imported_custom_type1`](#with_imported_custom_type1).
pub fn with_imported_custom_type7(
module: import_.ImportReference,
name: String,
type_: custom.CustomTypeBuilder(
repr,
custom.Generics7(a, b, c, d, e, f, g),
generics,
),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
constructor.Constructor(repr, d, generics),
constructor.Constructor(repr, e, generics),
constructor.Constructor(repr, f, generics),
constructor.Constructor(repr, g, generics),
) ->
Module,
) -> Module {
let assert [
variant7,
variant6,
variant5,
variant4,
variant3,
variant2,
variant1,
] = type_.variants
handler(
custom.new_custom_type(option.Some(module), name),
constructor.new(option.Some(module), variant1),
constructor.new(option.Some(module), variant2),
constructor.new(option.Some(module), variant3),
constructor.new(option.Some(module), variant4),
constructor.new(option.Some(module), variant5),
constructor.new(option.Some(module), variant6),
constructor.new(option.Some(module), variant7),
)
}
/// Define a custom type with eight variants.
/// See [`with_custom_type1`](#with_custom_type1).
pub fn with_custom_type8(
details: definition.Definition,
type_: custom.CustomTypeBuilder(
repr,
custom.Generics8(a, b, c, d, e, f, g, h),
generics,
),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
constructor.Constructor(repr, d, generics),
constructor.Constructor(repr, e, generics),
constructor.Constructor(repr, f, generics),
constructor.Constructor(repr, g, generics),
constructor.Constructor(repr, h, generics),
) ->
Module,
) -> Module {
let assert [
variant8,
variant7,
variant6,
variant5,
variant4,
variant3,
variant2,
variant1,
] = type_.variants
let rest =
handler(
custom.new_custom_type(option.None, definition.get_name(details)),
constructor.new(option.None, variant1),
constructor.new(option.None, variant2),
constructor.new(option.None, variant3),
constructor.new(option.None, variant4),
constructor.new(option.None, variant5),
constructor.new(option.None, variant6),
constructor.new(option.None, variant7),
constructor.new(option.None, variant8),
)
Module(..rest, definitions: [
Definition(details:, value: CustomTypeBuilder(type_ |> custom.to_dynamic())),
..rest.definitions
])
}
/// Import a custom type with eight variants.
/// See [`with_imported_custom_type1`](#with_imported_custom_type1).
pub fn with_imported_custom_type8(
module: import_.ImportReference,
name: String,
type_: custom.CustomTypeBuilder(
repr,
custom.Generics8(a, b, c, d, e, f, g, h),
generics,
),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
constructor.Constructor(repr, d, generics),
constructor.Constructor(repr, e, generics),
constructor.Constructor(repr, f, generics),
constructor.Constructor(repr, g, generics),
constructor.Constructor(repr, h, generics),
) ->
Module,
) -> Module {
let assert [
variant8,
variant7,
variant6,
variant5,
variant4,
variant3,
variant2,
variant1,
] = type_.variants
handler(
custom.new_custom_type(option.Some(module), name),
constructor.new(option.Some(module), variant1),
constructor.new(option.Some(module), variant2),
constructor.new(option.Some(module), variant3),
constructor.new(option.Some(module), variant4),
constructor.new(option.Some(module), variant5),
constructor.new(option.Some(module), variant6),
constructor.new(option.Some(module), variant7),
constructor.new(option.Some(module), variant8),
)
}
/// Define a custom type with nine variants.
/// See [`with_custom_type1`](#with_custom_type1).
pub fn with_custom_type9(
details: definition.Definition,
type_: custom.CustomTypeBuilder(
repr,
custom.Generics9(a, b, c, d, e, f, g, h, i),
generics,
),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
constructor.Constructor(repr, d, generics),
constructor.Constructor(repr, e, generics),
constructor.Constructor(repr, f, generics),
constructor.Constructor(repr, g, generics),
constructor.Constructor(repr, h, generics),
constructor.Constructor(repr, i, generics),
) ->
Module,
) -> Module {
let assert [
variant9,
variant8,
variant7,
variant6,
variant5,
variant4,
variant3,
variant2,
variant1,
] = type_.variants
let rest =
handler(
custom.new_custom_type(option.None, definition.get_name(details)),
constructor.new(option.None, variant1),
constructor.new(option.None, variant2),
constructor.new(option.None, variant3),
constructor.new(option.None, variant4),
constructor.new(option.None, variant5),
constructor.new(option.None, variant6),
constructor.new(option.None, variant7),
constructor.new(option.None, variant8),
constructor.new(option.None, variant9),
)
Module(..rest, definitions: [
Definition(details:, value: CustomTypeBuilder(type_ |> custom.to_dynamic())),
..rest.definitions
])
}
/// Import a custom type with nine variants.
/// See [`with_imported_custom_type1`](#with_imported_custom_type1).
pub fn with_imported_custom_type9(
module: import_.ImportReference,
name: String,
type_: custom.CustomTypeBuilder(
repr,
custom.Generics9(a, b, c, d, e, f, g, h, i),
generics,
),
handler: fn(
custom.CustomType(repr, generics),
constructor.Constructor(repr, a, generics),
constructor.Constructor(repr, b, generics),
constructor.Constructor(repr, c, generics),
constructor.Constructor(repr, d, generics),
constructor.Constructor(repr, e, generics),
constructor.Constructor(repr, f, generics),
constructor.Constructor(repr, g, generics),
constructor.Constructor(repr, h, generics),
constructor.Constructor(repr, i, generics),
) ->
Module,
) -> Module {
let assert [
variant9,
variant8,
variant7,
variant6,
variant5,
variant4,
variant3,
variant2,
variant1,
] = type_.variants
handler(
custom.new_custom_type(option.Some(module), name),
constructor.new(option.Some(module), variant1),
constructor.new(option.Some(module), variant2),
constructor.new(option.Some(module), variant3),
constructor.new(option.Some(module), variant4),
constructor.new(option.Some(module), variant5),
constructor.new(option.Some(module), variant6),
constructor.new(option.Some(module), variant7),
constructor.new(option.Some(module), variant8),
constructor.new(option.Some(module), variant9),
)
}
// }}}
/// Define a custom type with a dynamic number of variants.
/// See [`with_custom_type1`](#with_custom_type1).
pub fn with_custom_type_dynamic(
details: definition.Definition,
type_: custom.CustomTypeBuilder(repr, Dynamic, generics),
handler: fn(
custom.CustomType(repr, generics),
List(constructor.Constructor(repr, Dynamic, generics)),
) ->
Module,
) -> Module {
let rest =
handler(
custom.new_custom_type(option.None, definition.get_name(details)),
type_.variants
|> list.reverse()
|> list.map(constructor.new(option.None, _)),
)
Module(..rest, definitions: [
Definition(details:, value: CustomTypeBuilder(type_ |> custom.to_dynamic())),
..rest.definitions
])
}
/// Import a custom type with a dynamic number of variants.
/// See [`with_imported_custom_type1`](#with_imported_custom_type1).
pub fn with_imported_custom_type_dynamic(
module: import_.ImportReference,
name: String,
type_: custom.CustomTypeBuilder(repr, Dynamic, generics),
handler: fn(
custom.CustomType(repr, generics),
List(constructor.Constructor(repr, Dynamic, generics)),
) ->
Module,
) -> Module {
handler(
custom.new_custom_type(option.Some(module), name),
type_.variants
|> list.reverse()
|> list.map(constructor.new(option.Some(module), _)),
)
}
pub fn with_type_alias(
details: definition.Definition,
type_: type_.GeneratedType(repr),
handler: fn(type_.GeneratedType(repr)) -> Module,
) -> Module {
let rest = handler(type_.raw(definition.get_name(details)))
Module(..rest, definitions: [
Definition(details:, value: TypeAlias(type_ |> type_.to_dynamic())),
..rest.definitions
])
}
pub fn eof() -> Module {
Module([], [], option.None, [])
}
pub fn render(
module: Module,
context: public_render.Context,
) -> public_render.Rendered {
let #(definitions_at_top, definitions_at_bottom, definitions_after) =
separate_definitions(module.definitions, [], [], dict.new())
let external_definitions = case module.external_module {
option.Some(external) -> external.definitions
option.None -> []
}
let all_definitions =
[
definitions_at_bottom,
external_definitions,
definitions_at_top,
]
|> list.flatten
|> list.reverse()
let import_references = list.map(module.imports, import_.import_to_reference)
let context = render.Context(..context, imports: import_references)
let #(details, rendered_defs) =
render_all_definitions(
all_definitions,
definitions_after,
option.None,
context,
render.empty_details,
[],
)
let implied_imports =
details.required_implied_imports
|> list.map(fn(module) {
import_.new(module) |> import_.with_predefined(True)
})
let rendered_imports =
module.imports
|> list.append(implied_imports)
|> list.sort(import_.compare)
|> import_.merge_imports
|> list.filter(fn(m) {
m.predefined
// Explicit `.{…}` imports must appear even if nothing references the module prefix.
|| !list.is_empty(m.exposing)
|| list.contains(
details.used_imports,
import_reference.get_module_representation(import_.import_to_reference(
m,
)),
)
})
|> list.map(fn(x) { x |> render_imported_module |> render.to_string() })
|> list.map(doc.from_string)
let documentation_comments =
doc.join(
list.map(module.module_documentation_comments, fn(comment) {
doc.from_string("//// " <> comment)
}),
doc.line,
)
|> doc.append(case module.module_documentation_comments, rendered_imports {
[], _ | _, [] -> doc.empty
_, _ -> doc.line
})
rendered_imports
|> doc.join(with: doc.line)
|> doc.prepend(documentation_comments)
|> doc.append(
case
list.is_empty(rendered_imports)
&& list.is_empty(module.module_documentation_comments)
{
True -> doc.empty
False -> doc.concat([doc.line, doc.line])
},
)
|> doc.append(doc.concat_join(rendered_defs, [doc.line, doc.line]))
|> render.Render(details:)
}
pub fn render_imported_module(module: import_.ImportedModule) -> render.Rendered {
// Gleam syntax is `import path.{items} as alias`, not `import path as alias.{items}`.
doc.concat([
doc.from_string(module.before_text),
doc.from_string("import "),
doc.from_string(string.join(module.name, "/")),
case module.exposing {
[] -> doc.empty
exposing ->
doc.concat([
doc.from_string(".{"),
doc.from_string(import_.exposing_to_string(exposing)),
doc.from_string("}"),
])
},
case module.alias {
option.Some(alias) ->
doc.concat([
doc.space,
doc.from_string("as"),
doc.space,
doc.from_string(alias),
])
option.None -> doc.empty
},
])
|> render.Render(details: render.empty_details)
}
fn render_all_definitions(
definitions: List(ModuleDefinition),
after_definition: dict.Dict(String, List(ModuleDefinition)),
last_definition_name: Option(String),
context: render.Context,
previous_details: render.RenderedDetails,
rendered_definitions: List(doc.Document),
) -> #(render.RenderedDetails, List(doc.Document)) {
let definitions_to_prepend =
last_definition_name
|> option.then(fn(name) {
case dict.get(after_definition, name) {
Ok(defs) -> option.Some(#(defs, dict.delete(after_definition, name)))
Error(_) -> option.None
}
})
case definitions_to_prepend {
option.Some(#(defs, new_after_definition)) -> {
render_all_definitions(
list.append(defs, definitions),
new_after_definition,
option.None,
context,
previous_details,
rendered_definitions,
)
}
option.None -> {
case definitions {
[definition, ..rest] -> {
let #(rendered_def, new_details) =
render_definition(definition, context)
render_all_definitions(
rest,
after_definition,
option.Some(definition.get_name(definition.details)),
context,
render.merge_details(previous_details, new_details),
[rendered_def, ..rendered_definitions],
)
}
[] -> {
#(previous_details, list.reverse(rendered_definitions))
}
}
}
}
}
fn separate_definitions(
definitions: List(ModuleDefinition),
at_top: List(ModuleDefinition),
at_bottom: List(ModuleDefinition),
after_definition: dict.Dict(String, List(ModuleDefinition)),
) -> #(
List(ModuleDefinition),
List(ModuleDefinition),
dict.Dict(String, List(ModuleDefinition)),
) {
case definitions {
[definition, ..rest] -> {
case definition.get_position(definition.details) {
definition.Top ->
separate_definitions(
rest,
[definition, ..at_top],
at_bottom,
after_definition,
)
definition.Bottom ->
separate_definitions(
rest,
at_top,
[definition, ..at_bottom],
after_definition,
)
definition.AfterDefinition(def_name) -> {
let after_definition =
dict.upsert(
after_definition,
def_name,
with: fn(currently_after_definition) {
case currently_after_definition {
option.Some(list) -> [definition, ..list]
option.None -> [definition]
}
},
)
separate_definitions(rest, at_top, at_bottom, after_definition)
}
}
}
[] -> #(at_top, at_bottom, after_definition)
}
}
fn render_definition(definition: ModuleDefinition, context) {
let name = definition.get_name(definition.details)
let #(rendered, details) = case definition.value {
Constant(value) -> {
let rendered_expr = expression.render(value, context)
#(
doc.concat([
doc.from_string("const "),
doc.from_string(name),
doc.space,
doc.from_string("="),
doc.space,
rendered_expr.doc,
]),
rendered_expr.details,
)
}
CustomTypeBuilder(type_) -> {
let rendered_type = custom.render(type_, context)
#(
doc.concat([
doc.from_string("type "),
doc.from_string(name),
rendered_type.doc,
]),
rendered_type.details,
)
}
TypeAlias(type_) -> {
let rendered_type = type_.render_type(type_, context)
#(
doc.concat([
doc.from_string("type "),
doc.from_string(name),
doc.space,
doc.from_string("="),
doc.space,
rendered_type
|> result.map(fn(v) { v.doc })
|> result.unwrap(doc.from_string("??")),
]),
rendered_type
|> result.map(fn(v) { v.details })
|> result.unwrap(render.empty_details),
)
}
Function(func) -> {
let rendered = function.render(func, context, option.Some(name))
#(rendered.doc, rendered.details)
}
Predefined(_, _, content, _source) -> {
#(doc.from_string(content), render.empty_details)
}
}
let full_doc =
definition.create_base_definition(definition.details)
|> doc.append(rendered)
#(full_doc, details)
}