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
- #add_fix_budget_options(options, settings) ⇒ void
- #add_fix_target_options(options, settings) ⇒ void
- #apply_fix!(path, original, report, fixer, selector) ⇒ void
-
#fix_options(arguments) ⇒ Hash[Symbol, untyped]
Keep command-local construction settings separate from reusable CLI state.
- #fixer_construction(grammar, settings) ⇒ [ IR::Grammar, Symbol, IR::Automaton ]
- #prepare_fixer(path, settings) ⇒ [ String, Fix ]
- #run_fix_command(arguments) ⇒ Integer
- #selected_fix_proposal(proposals, selector) ⇒ Hash[Symbol, untyped]
- #write_fix_report(report, format) ⇒ void
Instance Method Details
#add_fix_budget_options(options, settings) ⇒ void
This method returns an undefined value.
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 (, 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| .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.
116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/ibex/cli/fix.rb', line 116 def (, settings) .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 .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.
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.
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 (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 || . = "Usage: ibex fix [options] GRAMMAR" (, settings) (, settings) .on("--algorithm=NAME", %w[slr lalr ielr lr1], "current construction algorithm") do |value| set_local_configuration_option(settings, :algorithm, value.to_sym) end .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 .on("--apply[=ID]", "atomically apply one safe source proposal") do |value| settings[:apply] = value || true end .on("--messages=FILE", "measure effects on an error-message catalog") do |value| settings[:messages] = value end .on("--format=FORMAT", %w[json text], "json or text") { |value| settings[:format] = value } .on("--help", "show help") { settings[:help] = .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 ]
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 ]
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) = settings[:messages] = ErrorMessages.load() if 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: , message_file: ) [source, fixer] end |
#run_fix_command(arguments) ⇒ 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 = (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]
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.
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 |