File: lazy.rb

package info (click to toggle)
ruby-bindata 2.4.14-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 600 kB
  • sloc: ruby: 8,566; makefile: 4
file content (109 lines) | stat: -rw-r--r-- 2,979 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
module BinData
  # A LazyEvaluator is bound to a data object.  The evaluator will evaluate
  # lambdas in the context of this data object.  These lambdas
  # are those that are passed to data objects as parameters, e.g.:
  #
  #    BinData::String.new(value: -> { %w(a test message).join(" ") })
  #
  # As a shortcut, :foo is the equivalent of lambda { foo }.
  #
  # When evaluating lambdas, unknown methods are resolved in the context of the
  # parent of the bound data object.  Resolution is attempted firstly as keys
  # in #parameters, and secondly as methods in this parent.  This
  # resolution propagates up the chain of parent data objects.
  #
  # An evaluation will recurse until it returns a result that is not
  # a lambda or a symbol.
  #
  # This resolution process makes the lambda easier to read as we just write
  # <tt>field</tt> instead of <tt>obj.field</tt>.
  class LazyEvaluator

    # Creates a new evaluator.  All lazy evaluation is performed in the
    # context of +obj+.
    def initialize(obj)
      @obj = obj
    end

    def lazy_eval(val, overrides = nil)
      @overrides = overrides if overrides
      if val.is_a? Symbol
        __send__(val)
      elsif callable?(val)
        instance_exec(&val)
      else
        val
      end
    end

    # Returns a LazyEvaluator for the parent of this data object.
    def parent
      if @obj.parent
        @obj.parent.lazy_evaluator
      else
        nil
      end
    end

    # Returns the index of this data object inside it's nearest container
    # array.
    def index
      return @overrides[:index] if defined?(@overrides) && @overrides.key?(:index)

      child = @obj
      parent = @obj.parent
      while parent
        if parent.respond_to?(:find_index_of)
          return parent.find_index_of(child)
        end
        child = parent
        parent = parent.parent
      end
      raise NoMethodError, "no index found"
    end

    def method_missing(symbol, *args)
      return @overrides[symbol] if defined?(@overrides) && @overrides.key?(symbol)

      if @obj.parent
        eval_symbol_in_parent_context(symbol, args)
      else
        super
      end
    end

    #---------------
    private

    def eval_symbol_in_parent_context(symbol, args)
      result = resolve_symbol_in_parent_context(symbol, args)
      recursively_eval(result, args)
    end

    def resolve_symbol_in_parent_context(symbol, args)
      obj_parent = @obj.parent

      if obj_parent.has_parameter?(symbol)
        obj_parent.get_parameter(symbol)
      elsif obj_parent.safe_respond_to?(symbol, true)
        obj_parent.__send__(symbol, *args)
      else
        symbol
      end
    end

    def recursively_eval(val, args)
      if val.is_a?(Symbol)
        parent.__send__(val, *args)
      elsif callable?(val)
        parent.instance_exec(&val)
      else
        val
      end
    end

    def callable?(obj)
      Proc === obj || Method === obj || UnboundMethod === obj
    end
  end
end