import deriving/location import glance import gleam/io import gleam/json import gleam/list import gleam/option import gleam/result import gleamgen/expression import gleamgen/expression/block import gleamgen/expression/statement import gleamgen/function import gleamgen/import_ import gleamgen/module import gleamgen/module/definition import gleamgen/parameter import gleamgen/render import gleamgen/source import gleamgen/type_.{type Dynamic} import justin import simplifile const location = "deriving/location:Other" pub fn main() -> Nil { let assert Ok(context) = location.new_context() let assert Ok(location) = location.parse_location(location) let assert Ok(#(definition, _context)) = location.get_type_at_location(context, location) let new_contents = update_module(definition, location) io.println(new_contents) } fn update_module(definition, location) { let assert Ok(contents) = simplifile.read("src/deriving/location.gleam") let assert Ok(source_map) = source.module_from_string(contents) let module = { use json_module <- module.with_import(import_.new(["gleam", "json"])) use decode_module <- module.with_import( import_.new(["gleam", "dynamic", "decode"]), ) let assert Ok(to_json_function) = create_to_json_function(definition, json_module) let type_name = definition.definition.name let to_json_definition = definition.new(justin.snake_case(type_name) <> "_to_json") |> definition.with_position(definition.AfterDefinition(type_name)) let assert Ok(decoder_function) = create_decoder_function(definition, decode_module) let decoder_definition = definition.new(justin.snake_case(type_name) <> "_decoder") |> definition.with_position(definition.AfterDefinition(type_name)) use _ <- module.with_function(to_json_definition, to_json_function) use _ <- module.with_function(decoder_definition, decoder_function) module.from_source_map(source_map) } module |> module.render(render.default_context()) |> render.to_string } fn create_to_json_function( definition: glance.Definition(glance.CustomType), json_module: import_.ImportReference, ) { let json_type: type_.GeneratedType(json.Json) = type_.raw("Json") // custom.to_type(import_.raw_type(json_module, "Json")) let handler_result = case definition.definition.variants { [] -> Error(Nil) [variant] -> { use object_encoders <- result.try( list.try_map(variant.fields, fn(field) { generate_json_encoder_for_field(field, json_module) }), ) let create_call = expression.call1( import_.value_of_type( json_module, "object", type_.reference(json.object), ), expression.list(object_encoders), ) Ok(fn(_data) { create_call }) } _variants -> Error(Nil) } use handler <- result.try(handler_result) Ok(function.new1( param1: parameter.new("data", type_.raw(definition.definition.name)), returns: json_type, handler: handler, )) } fn generate_json_encoder_for_field( field: glance.VariantField, json_module: import_.ImportReference, ) -> Result(expression.Expression(#(String, json.Json)), Nil) { use encoder <- result.try(type_to_encoder(field.item, json_module)) case field { glance.LabelledVariantField(label:, ..) -> Ok(expression.tuple2( expression.string(label), expression.call1(encoder, expression.raw("data." <> label)), )) _ -> Error(Nil) } } fn type_to_encoder( item: glance.Type, json_module: import_.ImportReference, ) -> Result(expression.Expression(fn(Dynamic) -> json.Json), Nil) { let wrap_in_function = fn(handler) { Ok( function.anonymous(function.new1( parameter.new("object", type_.dynamic()), type_.reference(json.bool(False)), handler, )), ) } case item { glance.NamedType(name: "String", module: option.None, ..) -> Ok(import_.raw_ident(json_module, "string")) glance.NamedType(name: "Int", module: option.None, ..) -> Ok(import_.raw_ident(json_module, "int")) glance.NamedType(name: "Bool", module: option.None, ..) -> Ok(import_.raw_ident(json_module, "bool")) glance.NamedType(name: "Float", module: option.None, ..) -> Ok(import_.raw_ident(json_module, "float")) glance.NamedType( name: "List", parameters: [list_type], module: option.None, .., ) -> { use encoder <- result.try(type_to_encoder(list_type, json_module)) let handler = fn(object) { expression.call2( import_.value_of_type( json_module, "array", type_.reference(json.array), ), expression.coerce_dynamic_unsafe(object), encoder, ) } wrap_in_function(handler) } glance.TupleType(elements:, ..) -> { let possible_encoders = list.try_map(elements, fn(element) { type_to_encoder(element, json_module) }) use encoders <- result.try(possible_encoders) let handler = fn(object) { let elements = encoders |> list.map(fn(encoder) { expression.call1(encoder, expression.coerce_dynamic_unsafe(object)) }) |> expression.list expression.call1( import_.value_of_type( json_module, "preprocessed_array", type_.reference(json.preprocessed_array), ), elements, ) } wrap_in_function(handler) } _ -> Error(Nil) } } fn create_decoder_function( definition: glance.Definition(glance.CustomType), decode_module: import_.ImportReference, ) { let handler_result = case definition.definition.variants { [variant] -> { let information_from_fields = list.try_map(variant.fields, fn(field) { use_decoder_for_field(field, decode_module) }) |> result.map(list.unzip) use #(handlers, fields) <- result.try(information_from_fields) Ok(fn() { use <- block.with_statements(handlers) expression.call_dynamic(expression.raw(variant.name), fields) }) } _ -> Error(Nil) } use handler <- result.try(handler_result) Ok(function.new0(returns: type_.dynamic(), handler:)) } fn use_decoder_for_field( field: glance.VariantField, decode_module: import_.ImportReference, ) -> Result(#(statement.Statement, expression.Expression(Dynamic)), Nil) { case field { glance.LabelledVariantField(label:, item:) -> { use decoder <- result.try(decoder_from_type(item, decode_module)) let use_statement = statement.dynamic_use( import_.raw_ident(decode_module, "field"), [expression.to_dynamic(expression.string(label)), decoder], [ label, ], ) Ok(#(use_statement, expression.raw(label))) } _ -> Error(Nil) } } fn decoder_from_type( item: glance.Type, decode_module: import_.ImportReference, ) -> Result(expression.Expression(Dynamic), Nil) { case item { glance.NamedType(name: "String", module: option.None, ..) -> Ok(import_.raw_ident(decode_module, "string")) glance.NamedType(name: "Int", module: option.None, ..) -> Ok(import_.raw_ident(decode_module, "int")) glance.NamedType(name: "Bool", module: option.None, ..) -> Ok(import_.raw_ident(decode_module, "bool")) glance.NamedType(name: "Float", module: option.None, ..) -> Ok(import_.raw_ident(decode_module, "float")) // glance.NamedType( // name: "List", // parameters: [list_type], // module: option.None, // .., // ) -> { // use decoder <- result.try(decoder_from_type(list_type, decode_module)) // Ok(expression.call1( // import_.value_of_type( // decode_module, // "list", // types.reference(decode_module), // ), // decoder, // )) // } // glance.TupleType(elements:, ..) -> { // let possible_decoders = // list.try_map(elements, fn(element) { // decoder_from_type(element, decode_module) // }) // use decoders <- result.try(possible_decoders) // Ok(expression.call1( // import_.value_of_type( // decode_module, // "tuple", // types.reference(decode_module), // ), // expression.list(decoders), // )) // } _ -> Error(Nil) } }