Current section

28 Versions

Jump to

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…