Packages
BinStruct is a library which provides you rich set of tools for parsing/encoding binaries
Current section
28 Versions
Jump to
Current section
28 Versions
Compare versions
6
files changed
+139
additions
-49
deletions
| @@ -3,7 +3,7 @@ | |
| 3 3 | <<"https://github.com/4ait/bin_struct/blob/master/CHANGELOG.md">>}, |
| 4 4 | {<<"GitHub">>,<<"https://github.com/4ait/bin_struct">>}]}. |
| 5 5 | {<<"name">>,<<"bin_struct">>}. |
| 6 | - {<<"version">>,<<"0.2.23">>}. |
| 6 | + {<<"version">>,<<"0.2.24">>}. |
| 7 7 | {<<"description">>, |
| 8 8 | <<"BinStruct is a library which provides you rich set of tools for parsing/encoding binaries">>}. |
| 9 9 | {<<"elixir">>,<<"~> 1.17">>}. |
| @@ -373,57 +373,128 @@ defmodule BinStruct do | |
| 373 373 | |
| 374 374 | @doc """ |
| 375 375 | |
| 376 | - Called in module with use BinStruct defined will implement options interface after struct fully parsed. |
| 376 | + # BinStruct Options Interface |
| 377 377 | |
| 378 | + ## Overview |
| 379 | + |
| 380 | + When a module with `use BinStruct` is defined, it will implement the options interface after the struct is fully parsed by default. |
| 381 | + |
| 382 | + The options interface allows for dynamic behavior based on field values, enabling more complex parsing logic and interdependencies between different parts of a binary structure. |
| 383 | + |
| 384 | + ## Basic Usage |
| 385 | + |
| 386 | + Options can be registered and implemented in a struct, and then used by other structs that reference it. |
| 387 | + |
| 388 | + ### With Separate Interface Module |
| 389 | + |
| 390 | + ```elixir |
| 391 | + defmodule SharedOptions do |
| 392 | + use BinStructOptionsInterface |
| 393 | + |
| 394 | + @type shared_option :: :a | :b |
| 395 | + |
| 396 | + register_option :shared_option |
| 397 | + end |
| 398 | + |
| 399 | + defmodule StructImplementingOptionsInterface do |
| 400 | + use BinStruct |
| 401 | + |
| 402 | + register_callback &impl_options_interface_1/1, data: :field |
| 403 | + |
| 404 | + impl_interface SharedOptions, &impl_options_interface_1/1 |
| 405 | + |
| 406 | + field :data, :binary, length: 1 |
| 407 | + |
| 408 | + defp impl_options_interface_1("A"), do: SharedOptions.option_shared_option(:a) |
| 409 | + defp impl_options_interface_1("B"), do: SharedOptions.option_shared_option(:b) |
| 410 | + end |
| 411 | + |
| 412 | + defmodule ParentOfStructImplementingOptionsInterface do |
| 413 | + use BinStruct |
| 414 | + |
| 415 | + register_callback &dependent_field_len/1, shared_option: %{ type: :option, interface: SharedOptions } |
| 416 | + |
| 417 | + field :child, StructImplementingOptionsInterface |
| 418 | + field :dependent_field, :binary, length_by: &dependent_field_len/1 |
| 419 | + |
| 420 | + defp dependent_field_len(:a), do: 1 |
| 421 | + defp dependent_field_len(:b), do: 2 |
| 422 | + end |
| 423 | + |
| 424 | + { :ok, _struct, "" = _rest } = ParentOfStructImplementingOptionsInterface.parse("A1") |
| 425 | + { :ok, _struct, "" = _rest } = ParentOfStructImplementingOptionsInterface.parse("B22") |
| 378 426 | ``` |
| 379 427 | |
| 380 | - iex> defmodule SharedOptions do |
| 381 | - ...> use BinStructOptionsInterface |
| 382 | - ...> |
| 383 | - ...> @type shared_option :: :a | :b |
| 384 | - ...> |
| 385 | - ...> register_option :shared_option |
| 386 | - ...> |
| 387 | - ...> end |
| 388 | - ...> |
| 389 | - ...> |
| 390 | - ...> defmodule StructImplementingOptionsInterface do |
| 391 | - ...> use BinStruct |
| 392 | - ...> |
| 393 | - ...> register_callback &impl_options_interface_1/1, data: :field |
| 394 | - ...> |
| 395 | - ...> impl_interface SharedOptions, &impl_options_interface_1/1 |
| 396 | - ...> |
| 397 | - ...> field :data, :binary, length: 1 |
| 398 | - ...> |
| 399 | - ...> defp impl_options_interface_1("A"), do: SharedOptions.option_shared_option(:a) |
| 400 | - ...> defp impl_options_interface_1("B"), do: SharedOptions.option_shared_option(:b) |
| 401 | - ...> |
| 402 | - ...> end |
| 403 | - ...> |
| 404 | - ...> defmodule ParentOfStructImplementingOptionsInterface do |
| 405 | - ...> use BinStruct |
| 406 | - ...> |
| 407 | - ...> register_callback &dependent_field_len/1, shared_option: %{ type: :option, interface: SharedOptions } |
| 408 | - ...> |
| 409 | - ...> field :child, StructImplementingOptionsInterface |
| 410 | - ...> field :dependent_field, :binary, length_by: &dependent_field_len/1 |
| 411 | - ...> |
| 412 | - ...> defp dependent_field_len(:a), do: 1 |
| 413 | - ...> defp dependent_field_len(:b), do: 2 |
| 414 | - ...> |
| 415 | - ...> end |
| 416 | - ...> |
| 417 | - ...> { :ok, _struct, "" = _rest } = ParentOfStructImplementingOptionsInterface.parse("A1") |
| 418 | - ...> { :ok, _struct, "" = _rest } = ParentOfStructImplementingOptionsInterface.parse("B22") |
| 428 | + ### Using Options Within the Same Struct |
| 419 429 | |
| 430 | + With the `for: :field_name` option, you can use options within the same struct, creating internal dependencies between fields. |
| 431 | + |
| 432 | + ```elixir |
| 433 | + defmodule ChildUsingOption do |
| 434 | + use BinStruct |
| 435 | + |
| 436 | + register_callback &validate_f1_equal_option_a2/2, |
| 437 | + f1: :field, |
| 438 | + a: :option |
| 439 | + |
| 440 | + register_option :a |
| 441 | + |
| 442 | + field :f1, :uint8, validate_by: &validate_f1_equal_option_a2/2 |
| 443 | + |
| 444 | + defp validate_f1_equal_option_a2(f1, a), do: f1 == a |
| 445 | + end |
| 446 | + |
| 447 | + defmodule ParentPassingOptions do |
| 448 | + use BinStruct |
| 449 | + |
| 450 | + register_callback &callback/1, |
| 451 | + f: :field |
| 452 | + |
| 453 | + field :f, :uint8 |
| 454 | + field :child, ChildUsingOption |
| 455 | + |
| 456 | + impl_interface ChildUsingOption, &callback/1, for: :child |
| 457 | + |
| 458 | + defp callback(f) do |
| 459 | + ChildUsingOption.option_a(f) |
| 460 | + end |
| 461 | + end |
| 462 | + |
| 463 | + # Example usage |
| 464 | + child = ChildUsingOption.new(f1: 1) |
| 465 | + parent = ParentPassingOptions.new(f: 1, child: child) |
| 466 | + parent_binary = ParentPassingOptions.dump_binary(parent) |
| 467 | + { :ok, parsed_parent, "" } = ParentPassingOptions.parse(parent_binary) |
| 420 468 | ``` |
| 421 469 | |
| 470 | + ## Key Concepts |
| 471 | + |
| 472 | + 1. **Options Registration**: Use `register_option :option_name` to define available options. |
| 473 | + |
| 474 | + 2. **Callbacks Registration**: Use `register_callback` to define functions that will determine option values. |
| 475 | + - Specify dependencies with `field_name: :field` for field dependencies |
| 476 | + - Specify option dependencies with `option_name: :option` |
| 477 | + |
| 478 | + 3. **Interface Implementation**: Use `impl_interface Module, &function/arity` to implement an options interface. |
| 479 | + - Add `for: :field_name` to specify the field for which the options are implemented. |
| 480 | + |
| 481 | + 4. **Validation**: Options can be used for validation through callbacks that verify field values against option values. |
| 482 | + |
| 483 | + ## Common Patterns |
| 484 | + |
| 485 | + ### Parent-Child Option Passing |
| 486 | + |
| 487 | + This pattern allows a parent struct to pass options to its child structs, enabling the child's behavior to be determined by the parent's field values. |
| 488 | + |
| 489 | + ### Self-Validating Structs |
| 490 | + |
| 491 | + By using the `for: :field_name` syntax, a struct can implement an options interface for itself, enabling field values to be validated against options derived from other fields within the same struct. |
| 492 | + |
| 422 493 | """ |
| 423 494 | |
| 424 | - defmacro impl_interface(interface, callback) do |
| 495 | + defmacro impl_interface(interface, callback, options \\ []) do |
| 425 496 | |
| 426 | - raw_interface_implementation = { interface, callback } |
| 497 | + raw_interface_implementation = { interface, callback, options } |
| 427 498 | |
| 428 499 | Module.put_attribute(__CALLER__.module, :interface_implementations, raw_interface_implementation) |
| @@ -94,12 +94,28 @@ defmodule BinStruct.Macro.Parse.ParseTopology do | |
| 94 94 | |
| 95 95 | interface_implementation_node = create_interface_implementation_node(interface_implementation) |
| 96 96 | |
| 97 | + %InterfaceImplementation{ force_call_before_parse_field_name: force_call_before_parse_field_name } = interface_implementation |
| 98 | + |
| 99 | + maybe_force_calling_order_connections = |
| 100 | + case force_call_before_parse_field_name do |
| 101 | + nil -> [] |
| 102 | + force_call_before_parse_field_name -> |
| 103 | + |
| 104 | + call_before_field = Enum.find(fields, fn field -> field.name == force_call_before_parse_field_name end) |
| 105 | + |
| 106 | + parse_node = create_parse_node(call_before_field) |
| 107 | + |
| 108 | + [{ interface_implementation_node, parse_node }] |
| 109 | + |
| 110 | + end |
| 111 | + |
| 97 112 | type_conversion_connections = create_type_conversion_connections_for_interface_implementation_node(interface_implementation_node, registered_callbacks_map) |
| 98 113 | virtual_field_producing_connections = create_virtual_field_producing_connections_for_interface_implementation_node(interface_implementation_node, registered_callbacks_map) |
| 99 114 | |
| 100 115 | List.flatten([ |
| 101 116 | type_conversion_connections, |
| 102 | - virtual_field_producing_connections |
| 117 | + virtual_field_producing_connections, |
| 118 | + maybe_force_calling_order_connections |
| 103 119 | ]) |
| 104 120 | |
| 105 121 | end |
| @@ -7,13 +7,14 @@ defmodule BinStruct.Macro.Preprocess.RemapInterfaceImplementation do | |
| 7 7 | |
| 8 8 | def remap_raw_interface_implementation(raw_interface_implementation, env) do |
| 9 9 | |
| 10 | - { interface, raw_callback } = raw_interface_implementation |
| 10 | + { interface, raw_callback, raw_options } = raw_interface_implementation |
| 11 11 | |
| 12 12 | callback = RemapCallback.remap_callback(raw_callback, env) |
| 13 13 | |
| 14 14 | %InterfaceImplementation{ |
| 15 15 | interface: interface, |
| 16 | - callback: callback |
| 16 | + callback: callback, |
| 17 | + force_call_before_parse_field_name: raw_options[:for] |
| 17 18 | } |
| 18 19 | |
| 19 20 | end |
| @@ -7,12 +7,14 @@ defmodule BinStruct.Macro.Structs.InterfaceImplementation do | |
| 7 7 | |
| 8 8 | @type t :: %InterfaceImplementation{ |
| 9 9 | interface: atom(), |
| 10 | - callback: Callback.t() |
| 10 | + callback: Callback.t(), |
| 11 | + force_call_before_parse_field_name: atom() | nil |
| 11 12 | } |
| 12 13 | |
| 13 14 | defstruct [ |
| 14 15 | :interface, |
| 15 | - :callback |
| 16 | + :callback, |
| 17 | + :force_call_before_parse_field_name |
| 16 18 | ] |
Loading more files…