Module: Ibex::CLIAnalysis

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

Overview

CLI entry points for deterministic grammar diff and metrics reports.

Instance Method Summary collapse

Instance Method Details

#algo_set?(settings) ⇒ Boolean

RBS:

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

Parameters:

  • settings (Hash[Symbol, untyped])

Returns:

  • (Boolean)


125
126
127
# File 'lib/ibex/cli/analysis.rb', line 125

def algo_set?(settings)
  settings.fetch(:configuration_explicit).include?(:algorithm)
end

#analysis_options(arguments, command, operands:) ⇒ Hash[Symbol, untyped]

RBS:

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

Parameters:

  • arguments (Array[String])
  • command (String)
  • operands: (String)

Returns:

  • (Hash[Symbol, untyped])


56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/ibex/cli/analysis.rb', line 56

def analysis_options(arguments, command, operands:)
  settings = {
    paths: [], algorithm: Configuration::Registry.fetch("parser.algorithm").default,
    mode: Configuration::Registry.fetch("grammar.mode").default, format: "json", configuration_explicit: []
  } #: Hash[Symbol, untyped]
  parser = OptionParser.new do |options|
    options.banner = "Usage: ibex #{command} [options] #{operands}"
    options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "algorithm for grammar inputs") 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("--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

#build_analysis_automaton(grammar, algorithm, algorithm_explicit) ⇒ IR::Automaton

RBS:

  • (IR::Grammar grammar, Symbol algorithm, bool algorithm_explicit) -> IR::Automaton

Parameters:

  • grammar (IR::Grammar)
  • algorithm (Symbol)
  • algorithm_explicit (Boolean)

Returns:



97
98
99
100
101
102
103
104
105
106
# File 'lib/ibex/cli/analysis.rb', line 97

def build_analysis_automaton(grammar, algorithm, algorithm_explicit)
  explicit_keys = algorithm_explicit ? [:algorithm] : [] #: Array[Symbol]
  active = activate_analysis_grammar(
    grammar, options: { algorithm: algorithm }, explicit_keys: explicit_keys
  )
  LALR::Builder.new(
    active, algorithm: configuration_value("parser.algorithm"),
            entry_isolation: configuration_value("parser.entries") == :isolated
  ).build
end

#load_analysis_automaton(path, algorithm, explicit:) ⇒ IR::Automaton

RBS:

  • (String path, Symbol algorithm, explicit: bool) -> IR::Automaton

Parameters:

  • path (String)
  • algorithm (Symbol)
  • explicit: (Boolean)

Returns:



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/ibex/cli/analysis.rb', line 79

def load_analysis_automaton(path, algorithm, explicit:)
  source = File.binread(path)
  if source.lstrip.start_with?("{")
    value = IR::Validator.validate(source)
    if value.is_a?(IR::Automaton)
      raise Ibex::Error, "(cli):1:1: --algorithm cannot be combined with Automaton IR analysis input" if explicit

      return value
    end
    return build_analysis_automaton(value, algorithm, explicit) if value.is_a?(IR::Grammar)

    raise Ibex::Error, "#{path}:1:1: analysis does not accept Lexer IR"
  end

  build_analysis_automaton(normalize_grammar_path(path), algorithm, explicit)
end

#run_diff_command(arguments) ⇒ Integer

RBS:

  • (Array[String] arguments) -> Integer

Parameters:

  • arguments (Array[String])

Returns:

  • (Integer)


22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/ibex/cli/analysis.rb', line 22

def run_diff_command(arguments)
  settings = analysis_options(arguments, "diff", operands: "OLD NEW")
  if settings[:help]
    @stdout.puts(settings.fetch(:help))
    return 0
  end
  paths = settings.fetch(:paths)
  raise Ibex::Error, "(diff):1:1: diff requires exactly two grammar or IR files" unless paths.length == 2

  algorithm = settings.fetch(:algorithm)
  before = load_analysis_automaton(paths.fetch(0), algorithm, explicit: algo_set?(settings))
  after = load_analysis_automaton(paths.fetch(1), algorithm, explicit: algo_set?(settings))
  report = Diff.new(before, after).to_h
  write_analysis_report(report, settings.fetch(:format))
  0
end

#run_metrics_command(arguments) ⇒ Integer

RBS:

  • (Array[String] arguments) -> Integer

Parameters:

  • arguments (Array[String])

Returns:

  • (Integer)


40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/ibex/cli/analysis.rb', line 40

def run_metrics_command(arguments)
  settings = analysis_options(arguments, "metrics", operands: "GRAMMAR")
  if settings[:help]
    @stdout.puts(settings.fetch(:help))
    return 0
  end
  paths = settings.fetch(:paths)
  raise Ibex::Error, "(metrics):1:1: metrics requires exactly one grammar or IR file" unless paths.length == 1

  automaton = load_analysis_automaton(paths.fetch(0), settings.fetch(:algorithm), explicit: algo_set?(settings))
  report = Metrics.new(automaton).to_h
  write_analysis_report(report, settings.fetch(:format))
  0
end

#write_analysis_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)


109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/ibex/cli/analysis.rb', line 109

def write_analysis_report(report, format)
  if format == "json"
    @stdout.puts(JSON.pretty_generate(report))
  elsif report.fetch(:ibex_report) == "metrics"
    @stdout.puts("states=#{report.dig(:automaton, :states)} rules=#{report.dig(:grammar, :rules)} " \
                 "alternatives=#{report.dig(:grammar, :alternatives)}")
  else
    %i[symbols rules conflicts warnings].each do |section|
      values = report.fetch(section)
      @stdout.puts("#{section}: +#{values.fetch(:added).length} " \
                   "-#{values.fetch(:removed).length} ~#{values.fetch(:changed).length}")
    end
  end
end