Module: Ibex::CLIIRTools

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

Overview

CLI validation and structural comparison for current IR documents.

Instance Method Summary collapse

Instance Method Details

#compare_grammars(before, after) ⇒ Hash[Symbol, Object?]

RBS:

  • (IR::Grammar before, IR::Grammar after) -> Hash[Symbol, Object?]

Parameters:

Returns:

  • (Hash[Symbol, Object?])


84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/ibex/cli/ir_tools.rb', line 84

def compare_grammars(before, after)
  before_symbols = before.symbols.map(&:name)
  after_symbols = after.symbols.map(&:name)
  before_productions = production_shapes(before)
  after_productions = production_shapes(after)
  {
    kind: "grammar",
    symbols: { added: (after_symbols - before_symbols).sort, removed: (before_symbols - after_symbols).sort },
    productions: {
      added: (after_productions - before_productions).sort,
      removed: (before_productions - after_productions).sort,
      count: numeric_change(before.productions.length, after.productions.length)
    },
    warnings: numeric_change(before.warnings.length, after.warnings.length)
  }
end

#compare_ir(before, after) ⇒ Hash[Symbol, Object?]

RBS:

  • (IR::Grammar | IR::Automaton | IR::Lexer before, IR::Grammar | IR::Automaton | IR::Lexer after) -> Hash[Symbol, Object?]

Parameters:

Returns:

  • (Hash[Symbol, Object?])


49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/ibex/cli/ir_tools.rb', line 49

def compare_ir(before, after)
  return compare_grammars(before, after) if before.is_a?(IR::Grammar) && after.is_a?(IR::Grammar)
  if before.is_a?(IR::Automaton) && after.is_a?(IR::Automaton)
    return {
      kind: "automaton",
      algorithm: { before: before.algorithm, after: after.algorithm },
      states: numeric_change(before.states.length, after.states.length),
      transitions: numeric_change(transition_count(before), transition_count(after)),
      conflicts: %i[sr resolved_sr rr].to_h do |kind|
        [kind, numeric_change(summary_count(before, kind), summary_count(after, kind))]
      end,
      grammar: compare_grammars(before.grammar, after.grammar)
    }
  end
  return compare_lexers(before, after) if before.is_a?(IR::Lexer) && after.is_a?(IR::Lexer)

  raise Ibex::Error, "(cli):1:1: unsupported IR comparison"
end

#compare_lexers(before, after) ⇒ Hash[Symbol, Object?]

RBS:

  • (IR::Lexer before, IR::Lexer after) -> Hash[Symbol, Object?]

Parameters:

Returns:

  • (Hash[Symbol, Object?])


69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/ibex/cli/ir_tools.rb', line 69

def compare_lexers(before, after)
  before_rules = before.rules.map { |rule| [rule.state, rule.kind, rule.token, rule.pattern, rule.options] }
  after_rules = after.rules.map { |rule| [rule.state, rule.kind, rule.token, rule.pattern, rule.options] }
  {
    kind: "lexer",
    states: { added: after.states - before.states, removed: before.states - after.states },
    rules: {
      added: after_rules - before_rules, removed: before_rules - after_rules,
      count: numeric_change(before.rules.length, after.rules.length)
    },
    warnings: numeric_change(before.warnings.length, after.warnings.length)
  }
end

#numeric_change(before, after) ⇒ Hash[Symbol, Integer]

RBS:

  • (Integer before, Integer after) -> Hash[Symbol, Integer]

Parameters:

  • before (Integer)
  • after (Integer)

Returns:

  • (Hash[Symbol, Integer])


124
125
126
# File 'lib/ibex/cli/ir_tools.rb', line 124

def numeric_change(before, after)
  { before: before, after: after, delta: after - before }
end

#production_shapes(grammar) ⇒ Array[String]

RBS:

  • (IR::Grammar grammar) -> Array[String]

Parameters:

Returns:

  • (Array[String])


102
103
104
105
106
107
108
# File 'lib/ibex/cli/ir_tools.rb', line 102

def production_shapes(grammar)
  grammar.productions.map do |production|
    lhs = grammar.symbol_by_id(production.lhs)&.name || production.lhs.to_s
    rhs = production.rhs.map { |id| grammar.symbol_by_id(id)&.name || id.to_s }
    "#{lhs} -> #{rhs.join(' ')}"
  end
end

#run_compare_command(arguments) ⇒ Integer

RBS:

  • (Array[String] arguments) -> Integer

Parameters:

  • arguments (Array[String])

Returns:

  • (Integer)


26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/ibex/cli/ir_tools.rb', line 26

def run_compare_command(arguments)
  raise Ibex::Error, "(cli):1:1: compare requires exactly two IR files" unless arguments.length == 2

  before = IR::Validator.validate(File.read(arguments.fetch(0)))
  after = IR::Validator.validate(File.read(arguments.fetch(1)))
  compatible = (before.is_a?(IR::Grammar) && after.is_a?(IR::Grammar)) ||
               (before.is_a?(IR::Automaton) && after.is_a?(IR::Automaton)) ||
               (before.is_a?(IR::Lexer) && after.is_a?(IR::Lexer))
  raise Ibex::Error, "(cli):1:1: cannot compare different IR kinds" unless compatible

  @stdout.puts(JSON.pretty_generate(compare_ir(before, after)))
  0
end

#run_validate_ir_command(arguments) ⇒ Integer

RBS:

  • (Array[String] arguments) -> Integer

Parameters:

  • arguments (Array[String])

Returns:

  • (Integer)


11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/ibex/cli/ir_tools.rb', line 11

def run_validate_ir_command(arguments)
  path = single_ir_path(arguments, "validate-ir")
  value = IR::Validator.validate(File.read(path))
  kind = if value.is_a?(IR::Grammar)
           "grammar"
         elsif value.is_a?(IR::Lexer)
           "lexer"
         else
           "automaton"
         end
  @stdout.puts("valid current #{kind} IR")
  0
end

#single_ir_path(arguments, command) ⇒ String

RBS:

  • (Array[String] arguments, String command) -> String

Parameters:

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

Returns:

  • (String)


41
42
43
44
45
# File 'lib/ibex/cli/ir_tools.rb', line 41

def single_ir_path(arguments, command)
  return arguments.first if arguments.length == 1

  raise Ibex::Error, "(cli):1:1: #{command} requires exactly one IR file"
end

#summary_count(automaton, kind) ⇒ Integer

RBS:

  • (IR::Automaton automaton, Symbol kind) -> Integer

Parameters:

Returns:

  • (Integer)


116
117
118
119
120
121
# File 'lib/ibex/cli/ir_tools.rb', line 116

def summary_count(automaton, kind)
  value = automaton.conflict_summary.fetch(kind)
  return value if value.is_a?(Integer)

  raise Ibex::Error, "(ir):1:1: conflict count #{kind} must be an integer"
end

#transition_count(automaton) ⇒ Integer

RBS:

  • (IR::Automaton automaton) -> Integer

Parameters:

Returns:

  • (Integer)


111
112
113
# File 'lib/ibex/cli/ir_tools.rb', line 111

def transition_count(automaton)
  automaton.states.sum { |state| state.transitions.length }
end