Module: Ibex::CLIFix

Defined in:
lib/ibex/cli/fix.rb,
sig/ibex/cli/fix.rbs

Overview

CLI entry point for bounded conflict-repair proposals. rubocop:disable Metrics/ModuleLength -- CLI option wiring and output projections share one closed contract.

Instance Method Summary collapse

Instance Method Details

#add_fix_budget_options(options, settings) ⇒ void

This method returns an undefined value.

RBS:

  • (OptionParser options, Hash[Symbol, untyped] settings) -> void

Parameters:

  • options (OptionParser)
  • settings (Hash[Symbol, untyped])


130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/ibex/cli/fix.rb', line 130

def add_fix_budget_options(options, settings)
  {
    "max-candidates" => :max_candidates,
    "max-builds" => :max_builds,
    "equiv-samples" => :equiv_samples,
    "equiv-max-tokens" => :equiv_max_tokens,
    "equiv-max-configurations" => :equiv_max_configurations,
    "verify-max-states" => :verify_max_states,
    "verify-max-items" => :verify_max_items
  }.each do |name, key|
    options.on("--#{name}=N", Integer, "positive bounded-search limit") do |value|
      raise OptionParser::InvalidArgument, "--#{name} must be positive" unless value.positive?

      settings[key] = value
    end
  end
end

#add_fix_target_options(options, settings) ⇒ void

This method returns an undefined value.

RBS:

  • (OptionParser options, Hash[Symbol, untyped] settings) -> void

Parameters:

  • options (OptionParser)
  • settings (Hash[Symbol, untyped])


116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/ibex/cli/fix.rb', line 116

def add_fix_target_options(options, settings)
  options.on("--state=N", Integer, "target state (default first unresolved conflict)") do |value|
    raise OptionParser::InvalidArgument, "--state must be nonnegative" if value.negative?

    settings[:state] = value
  end
  options.on("--conflict-index=N", Integer, "target conflict index within the state") do |value|
    raise OptionParser::InvalidArgument, "--conflict-index must be nonnegative" if value.negative?

    settings[:conflict_index] = value
  end
end

#apply_fix!(path, original, report, fixer, selector) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, String original, Hash[Symbol, untyped] report, Fix fixer, String | true selector) -> void

Parameters:

  • path (String)
  • original (String)
  • report (Hash[Symbol, untyped])
  • fixer (Fix)
  • selector (String, true)


159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/ibex/cli/fix.rb', line 159

def apply_fix!(path, original, report, fixer, selector)
  proposal = selected_fix_proposal(report.fetch(:proposals), selector)
  unless proposal.fetch(:applyable)
    raise Ibex::Error, "(fix):1:1: proposal #{proposal.fetch(:id)} changes invocation options, not source"
  end
  if File.symlink?(path) || File.stat(path).nlink > 1
    raise Ibex::Error, "(fix):1:1: --apply refuses symlink aliases and files with multiple hard links"
  end

  replacement = fixer.sources.fetch(proposal.fetch(:id))
  activate_cli_feature(:CLIFormatting)
  results = [{
    path: path, label: path, source: original, formatted: replacement
  }]
  targets = send(:formatting_targets, results)
  send(:transactionally_write_formatted, targets)
  report[:applied] = proposal.fetch(:id)
end

#fix_options(arguments) ⇒ Hash[Symbol, untyped]

Keep command-local construction settings separate from reusable CLI state.

RBS:

  • (Array[String] arguments) -> Hash[Symbol, untyped]

Parameters:

  • arguments (Array[String])

Returns:

  • (Hash[Symbol, untyped])


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
110
111
112
113
# File 'lib/ibex/cli/fix.rb', line 82

def fix_options(arguments)
  settings = {
    paths: [], algorithm: Configuration::Registry.fetch("parser.algorithm").default,
    mode: Configuration::Registry.fetch("grammar.mode").default, format: "json",
    max_candidates: 32, max_builds: 32, equiv_samples: 100,
    equiv_max_tokens: 8, equiv_max_configurations: 50_000,
    verify_max_states: 100_000, verify_max_items: 1_000_000, configuration_explicit: []
  } #: Hash[Symbol, untyped]
  parser = OptionParser.new do |options|
    options.banner = "Usage: ibex fix [options] GRAMMAR"
    add_fix_target_options(options, settings)
    add_fix_budget_options(options, settings)
    options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "current construction algorithm") do |value|
      set_local_configuration_option(settings, :algorithm, value.to_sym)
    end
    options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
      set_local_configuration_option(settings, :mode, value.to_sym)
      set_configuration_option(:mode, value.to_sym)
    end
    options.on("--apply[=ID]", "atomically apply one safe source proposal") do |value|
      settings[:apply] = value || true
    end
    options.on("--messages=FILE", "measure effects on an error-message catalog") do |value|
      settings[:messages] = value
    end
    options.on("--format=FORMAT", %w[json text], "json or text") { |value| settings[:format] = value }
    options.on("--help", "show help") { settings[:help] = options.to_s }
  end
  settings[:paths] = parser.parse(arguments)
  settings[:algorithm] = local_configuration_value(settings, "parser.algorithm")
  settings
end

#fixer_construction(grammar, settings) ⇒ [ IR::Grammar, Symbol, IR::Automaton ]

RBS:

  • (IR::Grammar grammar, Hash[Symbol, untyped] settings) -> [IR::Grammar, Symbol, IR::Automaton]

Parameters:

  • grammar (IR::Grammar)
  • settings (Hash[Symbol, untyped])

Returns:



149
150
151
152
# File 'lib/ibex/cli/fix.rb', line 149

def fixer_construction(grammar, settings)
  explicit_keys = settings.fetch(:configuration_explicit) & [:algorithm]
  construct_analysis_automaton(grammar, { algorithm: settings.fetch(:algorithm) }, explicit_keys)
end

#prepare_fixer(path, settings) ⇒ [ String, Fix ]

RBS:

  • (String path, Hash[Symbol, untyped] settings) -> [String, Fix]

Parameters:

  • path (String)
  • settings (Hash[Symbol, untyped])

Returns:

  • ([ String, Fix ])


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
# File 'lib/ibex/cli/fix.rb', line 47

def prepare_fixer(path, settings)
  source = File.binread(path)
  if BisonImport.bison_source?(source)
    imported = BisonImport::Importer.new(source, file: path).run
    unless imported.structurally_complete?
      names = imported.structural_unsupported.map(&:name).uniq.sort.join(", ")
      raise Ibex::Error,
            "(fix):1:1: Bison import is structurally incomplete due to: #{names}"
    end
    raise Ibex::Error,
          "(fix):1:1: import Bison source to a canonical analysis file before requesting source repairs"
  end
  grammar = normalize_grammar_path(path)
  grammar, algorithm, automaton = fixer_construction(grammar, settings)
  message_file = settings[:messages]
  messages = ErrorMessages.load(message_file) if message_file
  mode = configuration_value("grammar.mode") #: Symbol
  fixer = Fix.new(
    source,
    file: path, grammar: grammar, automaton: automaton,
    algorithm: algorithm, mode: mode,
    state: settings[:state], conflict_index: settings[:conflict_index],
    max_candidates: settings.fetch(:max_candidates), max_builds: settings.fetch(:max_builds),
    equiv_samples: settings.fetch(:equiv_samples),
    equiv_max_tokens: settings.fetch(:equiv_max_tokens),
    equiv_max_configurations: settings.fetch(:equiv_max_configurations),
    verify_max_states: settings.fetch(:verify_max_states),
    verify_max_items: settings.fetch(:verify_max_items),
    messages: messages, message_file: message_file
  )
  [source, fixer]
end

#run_fix_command(arguments) ⇒ Integer

RBS:

  • (Array[String] arguments) -> Integer

Parameters:

  • arguments (Array[String])

Returns:

  • (Integer)


24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/ibex/cli/fix.rb', line 24

def run_fix_command(arguments)
  settings = fix_options(arguments)
  if settings[:help]
    @stdout.puts(settings.fetch(:help))
    return 0
  end
  paths = settings.fetch(:paths)
  raise Ibex::Error, "(fix):1:1: fix requires exactly one grammar source file" unless paths.length == 1

  path = paths.fetch(0)
  source, fixer = prepare_fixer(path, settings)
  report = fixer.run
  apply_fix!(path, source, report, fixer, settings.fetch(:apply)) if settings[:apply]
  write_fix_report(report, settings.fetch(:format))
  proposals = report.fetch(:proposals) #: Array[Hash[Symbol, untyped]]
  proposals.empty? ? 1 : 0
rescue Fix::BudgetExceeded => e
  report = { ibex_report: "fix", schema_version: Fix::SCHEMA_VERSION }.merge(e.details)
  write_fix_report(report, settings&.fetch(:format) || "json")
  2
end

#selected_fix_proposal(proposals, selector) ⇒ Hash[Symbol, untyped]

RBS:

  • (Array[Hash[Symbol, untyped]] proposals, String | true selector) -> Hash[Symbol, untyped]

Parameters:

  • proposals (Array[Hash[Symbol, untyped]])
  • selector (String, true)

Returns:

  • (Hash[Symbol, untyped])


179
180
181
182
183
184
185
186
# File 'lib/ibex/cli/fix.rb', line 179

def selected_fix_proposal(proposals, selector)
  proposal = if selector == true
               proposals.find { |entry| entry.fetch(:applyable) }
             else
               proposals.find { |entry| entry.fetch(:id) == selector }
             end
  proposal || raise(Ibex::Error, "(fix):1:1: no matching applyable safe proposal")
end

#write_fix_report(report, format) ⇒ void

This method returns an undefined value.

RBS:

  • (Hash[Symbol, untyped] report, String format) -> void

Parameters:

  • report (Hash[Symbol, untyped])
  • format (String)


189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/ibex/cli/fix.rb', line 189

def write_fix_report(report, format)
  if format == "json"
    @stdout.puts(JSON.pretty_generate(report))
    return
  end

  @stdout.puts("result=#{report.fetch(:result)} proposals=#{report.fetch(:proposals, []).length}")
  report.fetch(:proposals, []).each do |proposal|
    @stdout.puts("#{proposal.fetch(:id)} #{proposal.fetch(:description)}")
  end
  @stdout.puts(report.fetch(:statement, Equiv::CAVEAT))
end