Module: Ibex::NormalizeParameters

Included in:
Normalizer
Defined in:
lib/ibex/normalize/parameters.rb,
sig/ibex/normalize/parameters.rbs

Overview

Iterative, hygienic specialization of validated parameterized user rules.

Instance Method Summary collapse

Instance Method Details

#clear_parameter_alternative(frame) ⇒ void

This method returns an undefined value.

RBS:

  • (Hash[Symbol, untyped] frame) -> void

Parameters:

  • frame (Hash[Symbol, untyped])


190
191
192
193
194
195
196
197
# File 'lib/ibex/normalize/parameters.rb', line 190

def clear_parameter_alternative(frame)
  frame[:current] = nil
  frame[:item_index] = 0
  frame[:rhs] = []
  frame[:named_refs] = []
  frame[:operations] = []
  frame[:values] = []
end

#drain_parameter_worklistvoid

This method returns an undefined value.

RBS:

  • () -> void



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/ibex/normalize/parameters.rb', line 137

def drain_parameter_worklist
  # @type self: Normalizer
  @parameter_worklist_active = true
  until @parameter_worklist.empty?
    frame = @parameter_worklist.last
    unless frame[:current] || prepare_parameter_alternative?(frame)
      @parameter_worklist.pop
      next
    end

    advance_parameter_alternative(frame)
  end
ensure
  @parameter_worklist_active = false
end

#enforce_parameter_limits!(reference) ⇒ void

This method returns an undefined value.

RBS:

  • (Frontend::AST::ParameterizedReference reference) -> void

Parameters:



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/ibex/normalize/parameters.rb', line 37

def enforce_parameter_limits!(reference)
  # @type self: Normalizer
  cyclic = @parameter_worklist.reverse.find do |frame|
    frame.fetch(:reference).name == reference.name &&
      growing_parameter_arguments?(reference.arguments, frame.fetch(:arguments))
  end
  if cyclic
    path = (@parameter_worklist.drop_while { |frame| frame != cyclic } + [{ reference: reference }])
           .map { |frame| frame.fetch(:reference).name }
    fail_at(reference.loc, "cyclic parameter specialization #{path.join(' -> ')}")
  end
  return unless @parameter_specializations.length >= @max_parameter_specializations

  fail_at(
    reference.loc,
    "parameter specialization limit of #{@max_parameter_specializations} exceeded"
  )
end

#growing_parameter_arguments?(candidates, ancestors) ⇒ Boolean

A constructor-growing recursive call can create infinitely many distinct specializations. Calls whose arguments shrink or merely change are allowed; they may reach a memoized fixed point after finite expansion.

RBS:

  • (Array[Frontend::AST::item] candidates, Array[Frontend::AST::item] ancestors) -> bool

Parameters:

  • candidates (Array[Frontend::AST::item])
  • ancestors (Array[Frontend::AST::item])

Returns:

  • (Boolean)


60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/ibex/normalize/parameters.rb', line 60

def growing_parameter_arguments?(candidates, ancestors)
  return false unless candidates.length == ancestors.length

  contained = candidates.each_index.all? do |index|
    candidate = candidates.fetch(index)
    ancestor = ancestors.fetch(index)
    parameter_argument_contains?(candidate, NormalizeExpression.render(ancestor))
  end
  contained && candidates.each_index.any? do |index|
    candidate = candidates.fetch(index)
    ancestor = ancestors.fetch(index)
    NormalizeExpression.render(candidate) != NormalizeExpression.render(ancestor)
  end
end

#new_parameter_helper(reference) ⇒ String

RBS:

  • (Frontend::AST::ParameterizedReference reference) -> String

Parameters:

Returns:

  • (String)


95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/ibex/normalize/parameters.rb', line 95

def new_parameter_helper(reference)
  # @type self: Normalizer
  @helper_sequence += 1
  name = "$parameter_#{@helper_sequence}"
  definition = intern(
    name, :nonterminal,
    location: reference.loc.to_h,
    documentation: @rule_documentation[reference.name],
    metadata_name: reference.name
  )
  if @inline_rule_names.include?(reference.name)
    @inline_symbol_ids << definition.id
    @inline_rule_by_symbol[definition.id] = reference.name
  end
  name
end

#parameter_argument_contains?(candidate, target) ⇒ Boolean

RBS:

  • (Frontend::AST::item candidate, String target) -> bool

Parameters:

  • candidate (Frontend::AST::item)
  • target (String)

Returns:

  • (Boolean)


76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/ibex/normalize/parameters.rb', line 76

def parameter_argument_contains?(candidate, target)
  pending = [candidate] #: Array[Frontend::AST::item]
  until pending.empty?
    item = pending.pop
    raise Ibex::Error, "missing parameter argument" unless item

    return true if NormalizeExpression.render(item) == target

    case item
    when Frontend::AST::ParameterizedReference then pending.concat(item.arguments)
    when Frontend::AST::Group then item.alternatives.each { |alternative| pending.concat(alternative) }
    when Frontend::AST::Optional, Frontend::AST::Star, Frontend::AST::Plus then pending << item.item
    when Frontend::AST::SeparatedList then pending.push(item.item, item.separator)
    end
  end
  false
end

#parameter_frame(reference, helper, arguments, rendered_arguments) ⇒ Hash[Symbol, untyped]

RBS:

  • (Frontend::AST::ParameterizedReference reference, String helper, Array[Frontend::AST::item] arguments, Array[String] rendered_arguments) -> Hash[Symbol, untyped]

Parameters:

Returns:

  • (Hash[Symbol, untyped])


115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/ibex/normalize/parameters.rb', line 115

def parameter_frame(reference, helper, arguments, rendered_arguments)
  # @type self: Normalizer
  formals = @parameter_formals.fetch(reference.name)
  {
    reference: reference,
    arguments: arguments,
    helper: helper,
    bindings: formals.zip(arguments).to_h,
    rendered_arguments: rendered_arguments,
    templates: @parameter_templates.fetch(reference.name),
    template_index: 0,
    alternative_index: 0,
    current: nil,
    item_index: 0,
    rhs: [],
    named_refs: [],
    operations: [],
    values: []
  }
end

#prepare_parameter_alternative?(frame) ⇒ Boolean

RBS:

  • (Hash[Symbol, untyped] frame) -> bool

Parameters:

  • frame (Hash[Symbol, untyped])

Returns:

  • (Boolean)


154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/ibex/normalize/parameters.rb', line 154

def prepare_parameter_alternative?(frame)
  # @type self: Normalizer
  templates = frame.fetch(:templates)
  while frame.fetch(:template_index) < templates.length
    template = templates.fetch(frame.fetch(:template_index))
    index = frame.fetch(:alternative_index)
    if index < template.alternatives.length
      prepare_parameter_alternative_entry(frame, template, index)
      return true
    end
    frame[:template_index] += 1
    frame[:alternative_index] = 0
  end
  false
end

#prepare_parameter_alternative_entry(frame, template, index) ⇒ void

This method returns an undefined value.

RBS:

  • (Hash[Symbol, untyped] frame, Frontend::AST::Rule template, Integer index) -> void

Parameters:



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/ibex/normalize/parameters.rb', line 171

def prepare_parameter_alternative_entry(frame, template, index)
  # @type self: Normalizer
  alternative = substitute_parameter_alternative(
    template.alternatives.fetch(index), frame.fetch(:bindings)
  )
  rule = Frontend::AST::Rule.new(
    lhs: frame.fetch(:helper), parameters: [], alternatives: [alternative], loc: template.loc,
    documentation: template.documentation, inline: false
  )
  frame[:alternative_index] = index + 1
  frame[:current] = [template, rule, alternative]
  frame[:item_index] = 0
  frame[:rhs] = []
  frame[:named_refs] = []
  frame[:operations] = []
  frame[:values] = []
end

#schedule_parameter_specialization(reference) ⇒ [ String, bool ]

RBS:

  • (Frontend::AST::ParameterizedReference reference) -> [String, bool]

Parameters:

Returns:

  • ([ String, bool ])


21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/ibex/normalize/parameters.rb', line 21

def schedule_parameter_specialization(reference)
  # @type self: Normalizer
  arguments = reference.arguments
  rendered_arguments = arguments.map { |argument| NormalizeExpression.render(argument) }
  key = [reference.name, rendered_arguments] #: [String, Array[String]]
  existing = @parameter_specializations[key]
  return [existing, false] if existing

  enforce_parameter_limits!(reference)
  helper = new_parameter_helper(reference)
  @parameter_specializations[key] = helper
  @parameter_worklist << parameter_frame(reference, helper, arguments, rendered_arguments)
  [helper, true]
end

#specialize_parameterized_reference(reference) ⇒ String

RBS:

  • (Frontend::AST::ParameterizedReference reference) -> String

Parameters:

Returns:

  • (String)


9
10
11
12
13
14
15
16
17
18
# File 'lib/ibex/normalize/parameters.rb', line 9

def specialize_parameterized_reference(reference)
  # @type self: Normalizer
  helper, scheduled = schedule_parameter_specialization(reference)
  if scheduled && @parameter_worklist_active
    raise Ibex::Error, "#{reference.loc}: internal parameter worklist ordering failure"
  end

  drain_parameter_worklist unless @parameter_worklist_active
  helper
end