Current section

Files

Jump to
tigerbeetlex tools elixir_bindings.zig
Raw

tools/elixir_bindings.zig

const std = @import("std");
const assert = std.debug.assert;
const vsr = @import("vsr");
const constants = vsr.constants;
const IO = vsr.io.IO;
const Tracer = vsr.trace.TracerType(vsr.time.Time);
const Storage = vsr.storage.StorageType(IO, Tracer);
const StateMachine = vsr.state_machine.StateMachineType(Storage, constants.state_machine_config);
const tb = vsr.tigerbeetle;
const tb_client = vsr.tb_client;
// Needed to configure VSR
pub const vsr_options = @import("config").vsr_options;
const auto_generated_code_notice =
\\#######################################################
\\# This file was auto-generated by elixir_bindings.zig #
\\# Do not manually modify. #
\\#######################################################
\\
;
const Constant = struct {
docs: []const u8,
name: []const u8,
value: []const u8,
};
const TypeMapping = struct {
file_name: []const u8,
module_name: []const u8,
hidden_fields: []const []const u8 = &.{},
docs_link: ?[]const u8 = null,
constants: []const Constant = &.{},
pub fn hidden(comptime self: @This(), name: []const u8) bool {
inline for (self.hidden_fields) |field| {
if (std.mem.eql(u8, field, name)) {
return true;
}
} else return false;
}
};
const type_mappings = .{
.{ tb.AccountFlags, TypeMapping{
.file_name = "account_flags",
.module_name = "AccountFlags",
.hidden_fields = &.{"padding"},
.docs_link = "reference/account#flags",
} },
.{ tb.TransferFlags, TypeMapping{
.file_name = "transfer_flags",
.module_name = "TransferFlags",
.hidden_fields = &.{"padding"},
.docs_link = "reference/transfer#flags",
} },
.{ tb.AccountFilterFlags, TypeMapping{
.file_name = "account_filter_flags",
.module_name = "AccountFilterFlags",
.hidden_fields = &.{"padding"},
.docs_link = "reference/account-filter#flags",
} },
.{ tb.QueryFilterFlags, TypeMapping{
.file_name = "query_filter_flags",
.module_name = "QueryFilterFlags",
.hidden_fields = &.{"padding"},
.docs_link = "reference/query-filter#flags",
} },
.{ tb.Account, TypeMapping{
.file_name = "account",
.module_name = "Account",
.hidden_fields = &.{"reserved"},
.docs_link = "reference/account#",
} },
.{ tb.AccountBalance, TypeMapping{
.file_name = "account_balance",
.module_name = "AccountBalance",
.hidden_fields = &.{"reserved"},
.docs_link = "reference/account-balance#",
} },
.{ tb.Transfer, TypeMapping{
.file_name = "transfer",
.module_name = "Transfer",
.docs_link = "reference/transfer#",
.constants = &.{
.{
.name = "amount_max",
.value = "Integer.pow(2, 128) - 1",
.docs = "The maximum amount for a transfer. Used in Two-Phase transfers to post the full pending amount.",
},
},
} },
.{ tb.QueryFilter, TypeMapping{
.file_name = "query_filter",
.module_name = "QueryFilter",
.hidden_fields = &.{"reserved"},
.docs_link = "reference/query-filter#",
} },
.{ tb.AccountFilter, TypeMapping{
.file_name = "account_filter",
.module_name = "AccountFilter",
.hidden_fields = &.{"reserved"},
.docs_link = "reference/account-filter#",
} },
.{ tb.CreateAccountResult, TypeMapping{
.file_name = "create_account_result",
.module_name = "CreateAccountResult",
.docs_link = "reference/requests/create_accounts#result",
} },
.{ tb.CreateTransferResult, TypeMapping{
.file_name = "create_transfer_result",
.module_name = "CreateTransferResult",
.docs_link = "reference/requests/create_transfers#result",
} },
.{ tb.CreateAccountsResult, TypeMapping{
.file_name = "create_accounts_result",
.module_name = "CreateAccountsResult",
.docs_link = "reference/requests/create_accounts#",
} },
.{ tb.CreateTransfersResult, TypeMapping{
.file_name = "create_transfers_result",
.module_name = "CreateTransfersResult",
.docs_link = "reference/requests/create_transfers#",
} },
};
fn emit_flags(
buffer: *std.ArrayList(u8),
comptime type_info: anytype,
comptime mapping: TypeMapping,
) !void {
assert(type_info.layout == .@"packed");
try buffer.writer().print(
\\{[notice]s}
\\defmodule TigerBeetlex.{[module_name]s} do
\\
, .{
.notice = auto_generated_code_notice,
.module_name = mapping.module_name,
});
try emit_docs(buffer, mapping, null);
try buffer.writer().print(
\\
\\
\\ use TypedStruct
\\
, .{});
try emit_typed_struct(buffer, type_info, mapping);
try buffer.writer().print(
\\
\\ @doc """
\\ Creates a `TigerBeetlex.{[module_name]s}` struct from its binary representation.
\\ """
\\ @spec from_binary(binary :: <<_::{[bit_size]}>>) :: t()
\\ def from_binary(<<n::unsigned-little-{[bit_size]}>> = _bin) do
\\ <<
\\
, .{
.module_name = mapping.module_name,
.bit_size = @bitSizeOf(type_info.backing_integer.?),
});
{
// Here we have to reverse the fields because they are laid out in little-endian
// order while when we match in Elixir we list things in big-endian order to
// avoid having to manually swap bytes
comptime var reversed_fields = std.mem.reverseIterator(type_info.fields);
inline while (reversed_fields.next()) |field| {
// Hidden == unused
const prefix = if (comptime mapping.hidden(field.name)) "_" else "";
try buffer.writer().print(
\\ {[prefix]s}{[field_name]s}::{[bit_size]},
\\
, .{
.prefix = prefix,
.field_name = field.name,
.bit_size = @bitSizeOf(field.type),
});
}
}
try buffer.writer().print(
\\ >> = <<n::unsigned-big-{[bit_size]}>>
\\
\\ %__MODULE__{{
\\
, .{
.bit_size = @bitSizeOf(type_info.backing_integer.?),
});
inline for (type_info.fields) |field| {
// Hidden == unused
if (comptime mapping.hidden(field.name)) continue;
try buffer.writer().print(
\\ {[field_name]s}: {[field_name]s} == 1,
\\
, .{
.field_name = field.name,
});
}
try buffer.writer().print(
\\ }}
\\ end
\\
\\ @doc """
\\ Converts a `TigerBeetlex.{[module_name]s}` struct to its binary represenation.
\\ """
\\ @spec to_binary(flags :: t()) :: <<_::{[bit_size]}>>
\\ def to_binary(flags) do
\\ %__MODULE__{{
\\
, .{
.module_name = mapping.module_name,
.bit_size = @bitSizeOf(type_info.backing_integer.?),
});
inline for (type_info.fields) |field| {
// Hidden == unused
if (comptime mapping.hidden(field.name)) continue;
try buffer.writer().print(
\\ {[field_name]s}: {[field_name]s},
\\
, .{
.field_name = field.name,
});
}
try buffer.writer().print(
\\ }} = flags
\\
\\ <<n::unsigned-big-{[bit_size]}>> =
\\ <<
\\
, .{
.bit_size = @bitSizeOf(type_info.backing_integer.?),
});
{
// Here we have to reverse the fields because they are laid out in little-endian
// order while when we match in Elixir we list things in big-endian order to
// avoid having to manually swap bytes
comptime var reversed_fields = std.mem.reverseIterator(type_info.fields);
inline while (reversed_fields.next()) |field| {
// We assume hidden == padded with zeroes
if (comptime mapping.hidden(field.name)) {
try buffer.writer().print(
\\ # {[field_name]s}
\\ 0::{[bit_size]},
\\
, .{
.field_name = field.name,
.bit_size = @bitSizeOf(field.type),
});
} else {
try buffer.writer().print(
\\ bool_to_u1({[field_name]s})::1,
\\
, .{
.field_name = field.name,
});
}
}
}
try buffer.writer().print(
\\ >>
\\
\\ <<n::unsigned-little-{[bit_size]}>>
\\ end
\\
\\ @spec bool_to_u1(b :: boolean()) :: 0 | 1
\\ defp bool_to_u1(true), do: 1
\\ defp bool_to_u1(false), do: 0
\\end
\\
, .{
.bit_size = @bitSizeOf(type_info.backing_integer.?),
});
}
fn emit_struct(
buffer: *std.ArrayList(u8),
comptime type_info: anytype,
comptime mapping: TypeMapping,
comptime struct_size: comptime_int,
) !void {
assert(type_info.layout == .@"extern");
try buffer.writer().print(
\\{[notice]s}
\\defmodule TigerBeetlex.{[module_name]s} do
\\
, .{
.notice = auto_generated_code_notice,
.module_name = mapping.module_name,
});
try emit_docs(buffer, mapping, null);
try buffer.writer().print(
\\
\\
\\ use TypedStruct
\\
, .{});
inline for (type_info.fields) |field| {
if (comptime mapping.hidden(field.name)) continue;
switch (@typeInfo(field.type)) {
.@"struct", .@"enum" => {
try buffer.writer().print(
\\
\\ alias TigerBeetlex.{[module_name]s}
, .{
.module_name = comptime module_name_for(type_mappings, field.type),
});
},
else => {},
}
}
try emit_typed_struct(buffer, type_info, mapping);
try emit_constants(buffer, mapping.constants);
try buffer.writer().print(
\\
\\ @doc """
\\ Creates a `TigerBeetlex.{[module_name]s}` struct from its binary representation.
\\ """
\\ @spec from_binary(binary :: <<_::{[bit_size]}>>) :: t()
\\ def from_binary(<<_::binary-size({[byte_size]})>> = bin) do
\\ <<
\\
, .{
.module_name = mapping.module_name,
.bit_size = struct_size * 8,
.byte_size = struct_size,
});
inline for (type_info.fields) |field| {
const prefix = if (comptime mapping.hidden(field.name)) "_" else "";
try buffer.writer().print(
\\ {[prefix]s}{[field_name]s}::{[bitstring_options]s},
\\
, .{
.prefix = prefix,
.field_name = field.name,
.bitstring_options = bitstring_options(field.name, field.type),
});
}
try buffer.writer().print(
\\ >> = bin
\\
\\ %__MODULE__{{
\\
, .{});
inline for (type_info.fields) |field| {
if (comptime mapping.hidden(field.name)) continue;
try buffer.writer().print(
\\ {[field_name]s}: {[nested_deserialization]s},
\\
, .{
.field_name = field.name,
.nested_deserialization = deserialize_struct_field(field.name, field.type),
});
}
try buffer.writer().print(
\\ }}
\\ end
\\
\\ @doc """
\\ Converts a `TigerBeetlex.{[module_name]s}` struct to its binary representation.
\\ """
\\ @spec to_binary(struct :: t()) :: <<_::{[bit_size]}>>
\\ def to_binary(struct) do
\\ %__MODULE__{{
\\
, .{
.module_name = mapping.module_name,
.bit_size = struct_size * 8,
});
inline for (type_info.fields) |field| {
if (comptime mapping.hidden(field.name)) continue;
try buffer.writer().print(
\\ {[field_name]s}: {[field_name]s},
\\
, .{
.field_name = field.name,
});
}
try buffer.writer().print(
\\ }} = struct
\\
\\ <<
\\
, .{});
inline for (type_info.fields) |field| {
// We assume hidden == padded with zeroes
if (comptime mapping.hidden(field.name)) {
try buffer.writer().print(
\\ # {[field_name]s}
\\ 0::unit(8)-size({[byte_size]}),
\\
, .{
.field_name = field.name,
.byte_size = @sizeOf(field.type),
});
continue;
}
try buffer.writer().print(
\\ {[serialized_value]s}::{[bitstring_options]s},
\\
, .{
.serialized_value = serialized_value(field.name, field.type),
.bitstring_options = bitstring_options(field.name, field.type),
});
}
try buffer.writer().print(
\\ >>
\\ end
\\end
\\
, .{});
}
fn emit_typed_struct(
buffer: *std.ArrayList(u8),
comptime type_info: anytype,
comptime mapping: TypeMapping,
) !void {
try buffer.writer().print(
\\
\\
\\ typedstruct do
\\
, .{});
inline for (type_info.fields) |field| {
if (comptime mapping.hidden(field.name)) continue;
const default_opt = if (comptime default_field_value(field.name, field.type)) |default|
std.fmt.comptimePrint(", default: {s}", .{default})
else
"";
try buffer.writer().print(
\\ field :{[field_name]s}, {[typespec_type]s}{[default_opt]s}
\\
, .{
.field_name = field.name,
.typespec_type = typespec_type(field.name, field.type),
.default_opt = default_opt,
});
}
try buffer.writer().print(
\\ end
\\
, .{});
}
fn emit_constants(
buffer: *std.ArrayList(u8),
comptime consts: []const Constant,
) !void {
for (consts) |constant| {
try buffer.writer().print(
\\
\\ @doc "{[docs]s}"
\\ def {[name]s} do
\\ {[value]s}
\\ end
\\
, .{
.docs = constant.docs,
.name = constant.name,
.value = constant.value,
});
}
}
fn typespec_type(comptime field_name: []const u8, comptime Type: type) []const u8 {
switch (@typeInfo(Type)) {
.bool => return "boolean()",
.int => |int| {
if (int.bits == 128 and !big_integer.contains(field_name))
return "<<_::128>>";
return if (int.signedness == .unsigned) "non_neg_integer()" else "integer()";
},
.@"enum" => return "atom()",
.@"struct" => return std.fmt.comptimePrint("{[module_name]s}.t()", .{
.module_name = comptime module_name_for(type_mappings, Type),
}),
else => unreachable,
}
}
fn bitstring_options(comptime field_name: []const u8, comptime Type: type) []const u8 {
switch (@typeInfo(Type)) {
.int => |info| {
if (info.bits == 128 and !big_integer.contains(field_name))
return "binary-size(16)";
return int_bitstring_options(info);
},
.@"enum" => |info| {
const tag_info = @typeInfo(info.tag_type);
assert(tag_info == .int);
return int_bitstring_options(tag_info.int);
},
.@"struct" => |info| {
assert(info.layout == .@"packed");
return std.fmt.comptimePrint("binary-size({[byte_size]})", .{
.byte_size = @sizeOf(Type),
});
},
// This should always be padding
.array => |info| {
assert(info.child == u8);
return std.fmt.comptimePrint("binary-size({[byte_size]})", .{
.byte_size = info.len,
});
},
else => unreachable,
}
}
fn int_bitstring_options(comptime info: std.builtin.Type.Int) []const u8 {
const signedness = if (info.signedness == .signed) "signed" else "unsigned";
return std.fmt.comptimePrint("{[signedness]s}-little-{[bits]}", .{
.signedness = signedness,
.bits = info.bits,
});
}
fn deserialize_struct_field(comptime field_name: []const u8, comptime Type: type) []const u8 {
switch (@typeInfo(Type)) {
.@"enum" => {
return std.fmt.comptimePrint("TigerBeetlex.{[module_name]s}.to_atom({[field_name]s})", .{
.module_name = comptime module_name_for(type_mappings, Type),
.field_name = field_name,
});
},
.@"struct" => |info| {
assert(info.layout == .@"packed");
return std.fmt.comptimePrint("TigerBeetlex.{[module_name]s}.from_binary({[field_name]s})", .{
.module_name = comptime module_name_for(type_mappings, Type),
.field_name = field_name,
});
},
.int => return field_name,
else => unreachable,
}
}
fn default_field_value(comptime field_name: []const u8, comptime Type: type) ?[]const u8 {
switch (@typeInfo(Type)) {
.bool => return "false",
.int => |info| {
if (info.bits == 128 and !big_integer.contains(field_name))
return "<<0::size(128)>>";
return "0";
},
.@"struct" => |info| {
assert(info.layout == .@"packed");
return std.fmt.comptimePrint("%{[module_name]s}{{}}", .{
.module_name = comptime module_name_for(type_mappings, Type),
});
},
else => return null,
}
}
fn serialized_value(comptime field_name: []const u8, comptime Type: type) []const u8 {
switch (@typeInfo(Type)) {
.int => return field_name,
.@"enum" => {
return std.fmt.comptimePrint("{[module_name]s}.from_atom({[field_name]s})", .{
.module_name = comptime module_name_for(type_mappings, Type),
.field_name = field_name,
});
},
.@"struct" => |info| {
assert(info.layout == .@"packed");
return std.fmt.comptimePrint(
"{[module_name]s}.to_binary({[field_name]s})",
.{
.module_name = comptime module_name_for(type_mappings, Type),
.field_name = field_name,
},
);
},
else => unreachable,
}
}
fn module_name_for(mappings: anytype, comptime Type: type) []const u8 {
comptime for (mappings) |mapping| {
const ZigType, const type_mapping = mapping;
if (ZigType == Type) return type_mapping.module_name;
};
unreachable;
}
/// Some 128-bit fields are better represented as numbers,
/// otherwise they are considered IDs and exposed as binaries.
const big_integer = struct {
const fields = .{
"credits_posted",
"credits_pending",
"debits_posted",
"debits_pending",
"amount",
};
fn contains(comptime field: []const u8) bool {
return comptime blk: for (fields) |value| {
if (std.mem.eql(u8, field, value)) break :blk true;
} else false;
}
};
fn emit_enum(
buffer: *std.ArrayList(u8),
comptime Type: type,
comptime mapping: TypeMapping,
) !void {
try buffer.writer().print(
\\{[notice]s}
\\defmodule TigerBeetlex.{[module_name]s} do
\\ @moduledoc false
\\
\\
, .{
.notice = auto_generated_code_notice,
.module_name = mapping.module_name,
});
try buffer.writer().print(
\\
\\ @doc """
\\ Obtains the atom representation of a result from its integer value.
\\ """
\\
, .{});
const type_info = @typeInfo(Type).@"enum";
inline for (type_info.fields) |field| {
if (comptime std.mem.startsWith(u8, field.name, "deprecated_")) continue;
if (comptime mapping.hidden(field.name)) continue;
try buffer.writer().print(
\\ def to_atom({[int_value]d}), do: :{[field]s}
\\
, .{
.int_value = @intFromEnum(@field(Type, field.name)),
.field = field.name,
});
}
try buffer.writer().print(
\\
\\ @doc """
\\ Obtains the integer representation of a result reason from its atom value.
\\ """
\\
, .{});
inline for (type_info.fields) |field| {
if (comptime std.mem.startsWith(u8, field.name, "deprecated_")) continue;
if (comptime mapping.hidden(field.name)) continue;
try buffer.writer().print(
\\
\\ def from_atom(:{[field]s}), do: {[int_value]}
, .{
.int_value = @intFromEnum(@field(Type, field.name)),
.field = field.name,
});
}
try buffer.writer().print(
\\
\\end
\\
, .{});
}
fn emit_response_module(
buffer: *std.ArrayList(u8),
) !void {
try buffer.writer().print(
\\defmodule TigerBeetlex.Response do
\\ @moduledoc """
\\ NIF Response parsing.
\\ This module is responsible for converting a response received from the TigerBeetle NIF to either
\\ an error or a list of results.
\\ """
\\
\\ @doc """
\\ Converts a NIF message response to a list of results.
\\
\\ If the response contains an error, `{{:error, reason}}` is returned.
\\
\\ If successful, it returns `{{:ok, list}}`. The type of the items of the list depend on the
\\ operation.
\\
\\
, .{});
{
const operation_info = @typeInfo(tb_client.Operation).@"enum";
inline for (operation_info.fields) |field| {
if (comptime std.mem.startsWith(u8, field.name, "deprecated_")) continue;
const operation: tb_client.Operation = @enumFromInt(field.value);
if (operation == .pulse or operation == .get_change_events) continue;
const result_type = StateMachine.ResultType(operation);
try buffer.writer().print(
\\ - `{[operation_name]s}`: a list of `%TigerBeetlex.{[result_module]s}{{}}`
\\
, .{
.operation_name = field.name,
.result_module = comptime module_name_for(type_mappings, result_type),
});
}
}
try buffer.writer().print(
\\ """
\\
, .{});
{
const packet_status_info = @typeInfo(tb_client.PacketStatus).@"enum";
inline for (packet_status_info.fields) |field| {
const status: tb_client.PacketStatus = @enumFromInt(field.value);
if (status == .ok) {
try buffer.writer().print(
\\ def decode({{{[ok_value]}, operation, batch}} = _response) do
\\ {{:ok, build_result_list(operation, batch)}}
\\ end
\\
\\
, .{
.ok_value = field.value,
});
} else {
try buffer.writer().print(
\\ def decode({{{[error_value]}, _operation, _batch}}) do
\\ {{:error, :{[error_name]s}}}
\\ end
\\
\\
, .{
.error_value = field.value,
.error_name = field.name,
});
}
}
}
{
const operation_info = @typeInfo(tb_client.Operation).@"enum";
inline for (operation_info.fields) |field| {
if (comptime std.mem.startsWith(u8, field.name, "deprecated_")) continue;
const operation: tb_client.Operation = @enumFromInt(field.value);
if (operation == .pulse or operation == .get_change_events) continue;
const result_type = StateMachine.ResultType(operation);
try buffer.writer().print(
\\ defp build_result_list({[operation_value]}, batch) when rem(bit_size(batch), {[result_bit_size]}) == 0 do
\\ for <<item::binary-size({[result_byte_size]}) <- batch>> do
\\ TigerBeetlex.{[result_module]s}.from_binary(item)
\\ end
\\ end
\\
\\
, .{
.operation_value = field.value,
.result_bit_size = @bitSizeOf(result_type),
.result_byte_size = @sizeOf(result_type),
.result_module = comptime module_name_for(type_mappings, result_type),
});
}
}
{
const packet_status_info = @typeInfo(tb_client.PacketStatus).@"enum";
try buffer.writer().print(
\\ @doc false
\\ def status_map do
\\ %{{
\\
, .{});
inline for (packet_status_info.fields) |field| {
try buffer.writer().print(
\\ {[error_name]s}: {[error_value]},
\\
, .{
.error_name = field.name,
.error_value = field.value,
});
}
try buffer.writer().print(
\\ }}
\\ end
\\
, .{});
}
try buffer.writer().print(
\\end
\\
, .{});
}
fn emit_operation_module(
buffer: *std.ArrayList(u8),
) !void {
try buffer.writer().print(
\\defmodule TigerBeetlex.Operation do
\\ @moduledoc false
\\
\\
, .{});
{
const operation_info = @typeInfo(tb_client.Operation).@"enum";
try buffer.writer().print(
\\ def available_operations do
\\ [
\\
, .{});
inline for (operation_info.fields) |field| {
if (comptime std.mem.startsWith(u8, field.name, "deprecated_")) continue;
const operation: tb_client.Operation = @enumFromInt(field.value);
if (operation == .pulse or operation == .get_change_events) continue;
try buffer.writer().print(
\\ :{[operation_name]s},
\\
, .{ .operation_name = field.name });
}
try buffer.writer().print(
\\ ]
\\ end
\\
\\
, .{});
}
{
const operation_info = @typeInfo(tb_client.Operation).@"enum";
inline for (operation_info.fields) |field| {
if (comptime std.mem.startsWith(u8, field.name, "deprecated_")) continue;
const operation: tb_client.Operation = @enumFromInt(field.value);
if (operation == .pulse or operation == .get_change_events) continue;
try buffer.writer().print(
\\ def from_atom(:{[operation_name]s}), do: {[operation_value]}
\\
, .{
.operation_name = field.name,
.operation_value = field.value,
});
}
}
try buffer.writer().print(
\\end
\\
, .{});
}
fn emit_docs(
buffer: anytype,
comptime mapping: TypeMapping,
comptime field: ?[]const u8,
) !void {
if (mapping.docs_link) |docs_link| {
try buffer.writer().print(
\\ @{[doc_type]s} """
\\ See [{[name]s}](https://docs.tigerbeetle.com/{[docs_link]s}{[field]s}).
\\ """
, .{
.doc_type = if (field == null) "moduledoc" else "doc",
.name = field orelse mapping.module_name,
.docs_link = docs_link,
.field = field orelse "",
});
}
}
pub fn generate_bindings(
comptime ZigType: type,
comptime mapping: TypeMapping,
buffer: *std.ArrayList(u8),
) !void {
@setEvalBranchQuota(100_000);
switch (@typeInfo(ZigType)) {
.@"struct" => |info| switch (info.layout) {
.auto => @compileError(
"Only packed or extern structs are supported: " ++ @typeName(ZigType),
),
.@"packed" => try emit_flags(buffer, info, mapping),
.@"extern" => try emit_struct(buffer, info, mapping, @sizeOf(ZigType)),
},
.@"enum" => try emit_enum(buffer, ZigType, mapping),
else => @compileError("Type cannot be represented: " ++ @typeName(ZigType)),
}
}
// TODO: accept this from the build system
const target_dir_path = "lib/tigerbeetlex/bindings";
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var target_dir = try std.fs.cwd().openDir(target_dir_path, .{});
defer target_dir.close();
// Emit Elixir declarations.
inline for (type_mappings) |type_mapping| {
const ZigType = type_mapping[0];
const mapping = type_mapping[1];
var buffer = std.ArrayList(u8).init(allocator);
try generate_bindings(ZigType, mapping, &buffer);
try target_dir.writeFile(.{
.sub_path = mapping.file_name ++ ".ex",
.data = buffer.items,
});
}
{
var buffer = std.ArrayList(u8).init(allocator);
try emit_response_module(&buffer);
try target_dir.writeFile(.{
.sub_path = "response.ex",
.data = buffer.items,
});
}
{
var buffer = std.ArrayList(u8).init(allocator);
try emit_operation_module(&buffer);
try target_dir.writeFile(.{
.sub_path = "operation.ex",
.data = buffer.items,
});
}
}