Class: Ibex::TableArtifact::TableValidator

Inherits:
Object
  • Object
show all
Includes:
ValidationSupport
Defined in:
lib/ibex/table_artifact/validator/tables.rb,
sig/ibex/table_artifact/validator/tables.rbs

Constant Summary

Constants included from ValidationSupport

ValidationSupport::DIGEST

Instance Method Summary collapse

Methods included from ValidationSupport

#array, #boolean, #digest, #enum, #integer, #invalid, #nullable_integer, #nullable_string, #record, #sorted_unique!, #string

Constructor Details

#initialize(data, state_count:, production_count:, terminal_ids:, nonterminal_ids:, representation:) ⇒ TableValidator

Returns a new instance of TableValidator.

RBS:

  • (Hash[String, ValidationSupport::json_value] data, state_count: Integer, production_count: Integer, terminal_ids: Set[Integer], nonterminal_ids: Set[Integer], representation: String) -> void

Parameters:

  • data (Hash[String, ValidationSupport::json_value])
  • state_count: (Integer)
  • production_count: (Integer)
  • terminal_ids: (Set[Integer])
  • nonterminal_ids: (Set[Integer])
  • representation: (String)


16
17
18
19
20
21
22
23
# File 'lib/ibex/table_artifact/validator/tables.rb', line 16

def initialize(data, state_count:, production_count:, terminal_ids:, nonterminal_ids:, representation:)
  @data = data
  @state_count = state_count
  @production_count = production_count
  @terminal_ids = terminal_ids
  @nonterminal_ids = nonterminal_ids
  @representation = representation
end

Instance Method Details

#validate!void

This method returns an undefined value.

RBS:

  • () -> void



26
27
28
29
30
31
# File 'lib/ibex/table_artifact/validator/tables.rb', line 26

def validate!
  record(@data, "$.payload.tables", %w[actions gotos default_actions])
  validate_actions(@data.fetch("actions"))
  validate_gotos(@data.fetch("gotos"))
  validate_defaults(@data.fetch("default_actions"))
end

#validate_action_code(code, path) ⇒ void

This method returns an undefined value.

RBS:

  • (ValidationSupport::json_value code, String path) -> void

Parameters:

  • code (ValidationSupport::json_value)
  • path (String)


222
223
224
225
226
227
228
229
# File 'lib/ibex/table_artifact/validator/tables.rb', line 222

def validate_action_code(code, path)
  code = integer(code, path)
  return if [0, -1].include?(code)
  return validate_state(code - 1, path) if code.positive?

  production = -2 - code
  invalid(path, "references a missing production") unless production.between?(0, @production_count - 1)
end

#validate_actions(table) ⇒ void

This method returns an undefined value.

RBS:

  • (ValidationSupport::json_value table) -> void

Parameters:

  • table (ValidationSupport::json_value)


36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/ibex/table_artifact/validator/tables.rb', line 36

def validate_actions(table)
  invalid("$.payload.tables.actions", "must be an object") unless table.is_a?(Hash)
  table_data = table #: Hash[String, ValidationSupport::json_value]
  encoding = table_data["encoding"]
  expected = @representation == "compact" ? "signed-row-displacement-v1" : "signed-sparse-rows-v1"
  invalid("$.payload.tables.actions.encoding", "does not match table representation") unless encoding == expected
  case encoding
  when "signed-sparse-rows-v1" then validate_sparse_actions(table_data)
  when "signed-row-displacement-v1" then validate_compact_actions(table_data)
  else invalid("$.payload.tables.actions.encoding", "is unsupported")
  end
end

#validate_canonical_displacement(table, rows, value_key:, width_key:, width:, dense:, path:) ⇒ void

This method returns an undefined value.

RBS:

  • (Hash[String, ValidationSupport::json_value] table, Array[Hash[Integer, ValidationSupport::json_value]] rows, value_key: String, width_key: String, width: Integer?, dense: bool, path: String) -> void

Parameters:

  • table (Hash[String, ValidationSupport::json_value])
  • rows (Array[Hash[Integer, ValidationSupport::json_value]])
  • value_key: (String)
  • width_key: (String)
  • width: (Integer, nil)
  • dense: (Boolean)
  • path: (String)


173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/ibex/table_artifact/validator/tables.rb', line 173

def validate_canonical_displacement(table, rows, value_key:, width_key:, width:, dense:, path:)
  canonical = Tables::Compact.build(rows, dense: dense)
  expected = [canonical.offsets, canonical.values, canonical.checks]
  actual = [table.fetch("offsets"), table.fetch(value_key), table.fetch("checks")]
  invalid(path, "must use the canonical minimal row-displacement layout") unless actual == expected

  expected_width = if width_key == "column_count"
                     rows.flat_map(&:keys).max.to_i + 1
                   else
                     canonical.dense_width
                   end
  expected_width = nil if expected_width && (@state_count * expected_width) > Tables::Compact::DENSE_CELL_LIMIT
  invalid("#{path}.#{width_key}", "does not match the canonical table width") unless width == expected_width
end

#validate_compact_actions(table) ⇒ void

This method returns an undefined value.

RBS:

  • (Hash[String, ValidationSupport::json_value] table) -> void

Parameters:

  • table (Hash[String, ValidationSupport::json_value])


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/ibex/table_artifact/validator/tables.rb', line 68

def validate_compact_actions(table)
  keys = %w[encoding row_count column_count offsets codes checks]
  record(table, "$.payload.tables.actions", keys)
  enum(table.fetch("encoding"), "$.payload.tables.actions.encoding", ["signed-row-displacement-v1"])
  validate_row_count(table, "$.payload.tables.actions")
  column_count = nullable_integer(table.fetch("column_count"), "$.payload.tables.actions.column_count",
                                  minimum: 1)
  rows = validate_displacement(table, value_key: "codes", path: "$.payload.tables.actions") do |column, code,
                                                                                                   cell_path|
    invalid(cell_path, "column must reference a terminal") unless @terminal_ids.include?(column)
    invalid(cell_path, "column exceeds column_count") if column_count && column >= column_count
    validate_token_action(code, column, "#{cell_path}.code")
  end
  validate_canonical_displacement(
    table, rows, value_key: "codes", width_key: "column_count", width: column_count, dense: false,
                 path: "$.payload.tables.actions"
  )
end

#validate_compact_gotos(table) ⇒ void

This method returns an undefined value.

RBS:

  • (Hash[String, ValidationSupport::json_value] table) -> void

Parameters:

  • table (Hash[String, ValidationSupport::json_value])


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/ibex/table_artifact/validator/tables.rb', line 120

def validate_compact_gotos(table)
  keys = %w[encoding row_count dense_width offsets values checks]
  record(table, "$.payload.tables.gotos", keys)
  enum(table.fetch("encoding"), "$.payload.tables.gotos.encoding", ["row-displacement-v1"])
  validate_row_count(table, "$.payload.tables.gotos")
  dense_width = nullable_integer(table.fetch("dense_width"), "$.payload.tables.gotos.dense_width", minimum: 1)
  rows = validate_displacement(table, value_key: "values", path: "$.payload.tables.gotos") do |column, state,
                                                                                                   cell_path|
    invalid(cell_path, "column must reference a nonterminal") unless @nonterminal_ids.include?(column)
    invalid(cell_path, "column exceeds dense_width") if dense_width && column >= dense_width
    validate_state(state, "#{cell_path}.state")
  end
  validate_canonical_displacement(
    table, rows, value_key: "values", width_key: "dense_width", width: dense_width, dense: true,
                 path: "$.payload.tables.gotos"
  )
end

#validate_defaults(defaults) ⇒ void

This method returns an undefined value.

RBS:

  • (ValidationSupport::json_value defaults) -> void

Parameters:

  • defaults (ValidationSupport::json_value)


198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/ibex/table_artifact/validator/tables.rb', line 198

def validate_defaults(defaults)
  values = array(defaults, "$.payload.tables.default_actions")
  unless values.length == @state_count
    invalid("$.payload.tables.default_actions",
            "must contain one entry per state")
  end
  values.each_with_index do |code, state|
    next if code.nil?

    path = "$.payload.tables.default_actions[#{state}]"
    code = integer(code, path)
    invalid(path, "must be an error or reduction") if code >= 0
  end
end

#validate_displacement(table, value_key:, path:) {|arg0, arg1, arg2| ... } ⇒ Array[Hash[Integer, ValidationSupport::json_value]]

RBS:

  • (Hash[String, ValidationSupport::json_value] table, value_key: String, path: String) { (Integer, ValidationSupport::json_value, String) -> void } -> Array[Hash[Integer, ValidationSupport::json_value]]

Parameters:

  • table (Hash[String, ValidationSupport::json_value])
  • value_key: (String)
  • path: (String)

Yields:

Yield Parameters:

  • arg0 (Integer)
  • arg1 (ValidationSupport::json_value)
  • arg2 (String)

Yield Returns:

  • (void)

Returns:

  • (Array[Hash[Integer, ValidationSupport::json_value]])


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/ibex/table_artifact/validator/tables.rb', line 146

def validate_displacement(table, value_key:, path:)
  offsets = integer_array(table.fetch("offsets"), "#{path}.offsets", allow_nil: false, minimum: 0)
  invalid("#{path}.offsets", "must contain one offset per state") unless offsets.length == @state_count
  minimum = value_key == "codes" ? nil : 0
  values = integer_array(table.fetch(value_key), "#{path}.#{value_key}", allow_nil: true, minimum: minimum)
  checks = integer_array(table.fetch("checks"), "#{path}.checks", allow_nil: true, minimum: 0)
  invalid(path, "#{value_key} and checks must have equal length") unless values.length == checks.length

  rows = Array.new(@state_count) { {} } #: Array[Hash[Integer, ValidationSupport::json_value]]
  checks.each_index do |index|
    row = checks[index]
    value = values[index]
    invalid("#{path}[#{index}]", "value and check occupancy must match") unless row.nil? == value.nil?
    next unless row

    invalid("#{path}.checks[#{index}]", "references a missing state") unless row.between?(0, @state_count - 1)
    column = index - offsets.fetch(row)
    invalid("#{path}[#{index}]", "has a negative column") if column.negative?
    yield(column, value, "#{path}[#{index}]")
    rows.fetch(row)[column] = value
  end
  rows
end

#validate_gotos(table) ⇒ void

This method returns an undefined value.

RBS:

  • (ValidationSupport::json_value table) -> void

Parameters:

  • table (ValidationSupport::json_value)


88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/ibex/table_artifact/validator/tables.rb', line 88

def validate_gotos(table)
  invalid("$.payload.tables.gotos", "must be an object") unless table.is_a?(Hash)
  table_data = table #: Hash[String, ValidationSupport::json_value]
  encoding = table_data["encoding"]
  expected = @representation == "compact" ? "row-displacement-v1" : "sparse-rows-v1"
  invalid("$.payload.tables.gotos.encoding", "does not match table representation") unless encoding == expected
  case encoding
  when "sparse-rows-v1" then validate_sparse_gotos(table_data)
  when "row-displacement-v1" then validate_compact_gotos(table_data)
  else invalid("$.payload.tables.gotos.encoding", "is unsupported")
  end
end

#validate_row_count(table, path) ⇒ void

This method returns an undefined value.

RBS:

  • (Hash[String, ValidationSupport::json_value] table, String path) -> void

Parameters:

  • table (Hash[String, ValidationSupport::json_value])
  • path (String)


139
140
141
142
# File 'lib/ibex/table_artifact/validator/tables.rb', line 139

def validate_row_count(table, path)
  count = integer(table.fetch("row_count"), "#{path}.row_count", minimum: 1)
  invalid("#{path}.row_count", "must equal state_count") unless count == @state_count
end

#validate_sparse_actions(table) ⇒ void

This method returns an undefined value.

RBS:

  • (Hash[String, ValidationSupport::json_value] table) -> void

Parameters:

  • table (Hash[String, ValidationSupport::json_value])


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/ibex/table_artifact/validator/tables.rb', line 50

def validate_sparse_actions(table)
  record(table, "$.payload.tables.actions", %w[encoding rows])
  rows = array(table.fetch("rows"), "$.payload.tables.actions.rows")
  invalid("$.payload.tables.actions.rows", "must contain one row per state") unless rows.length == @state_count
  rows.each_with_index do |row, state|
    token_ids = array(row, "$.payload.tables.actions.rows[#{state}]").map.with_index do |cell, index|
      path = "$.payload.tables.actions.rows[#{state}][#{index}]"
      cell = record(cell, path, %w[token_id code])
      token_id = integer(cell.fetch("token_id"), "#{path}.token_id", minimum: 0)
      invalid("#{path}.token_id", "must reference a terminal") unless @terminal_ids.include?(token_id)
      validate_token_action(cell.fetch("code"), token_id, "#{path}.code")
      token_id
    end
    sorted_unique!(token_ids, "$.payload.tables.actions.rows[#{state}]")
  end
end

#validate_sparse_gotos(table) ⇒ void

This method returns an undefined value.

RBS:

  • (Hash[String, ValidationSupport::json_value] table) -> void

Parameters:

  • table (Hash[String, ValidationSupport::json_value])


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/ibex/table_artifact/validator/tables.rb', line 102

def validate_sparse_gotos(table)
  record(table, "$.payload.tables.gotos", %w[encoding rows])
  rows = array(table.fetch("rows"), "$.payload.tables.gotos.rows")
  invalid("$.payload.tables.gotos.rows", "must contain one row per state") unless rows.length == @state_count
  rows.each_with_index do |row, state|
    symbol_ids = array(row, "$.payload.tables.gotos.rows[#{state}]").map.with_index do |cell, index|
      path = "$.payload.tables.gotos.rows[#{state}][#{index}]"
      cell = record(cell, path, %w[symbol_id state])
      symbol_id = integer(cell.fetch("symbol_id"), "#{path}.symbol_id", minimum: 0)
      invalid("#{path}.symbol_id", "must reference a nonterminal") unless @nonterminal_ids.include?(symbol_id)
      validate_state(cell.fetch("state"), "#{path}.state")
      symbol_id
    end
    sorted_unique!(symbol_ids, "$.payload.tables.gotos.rows[#{state}]")
  end
end

#validate_state(state, path) ⇒ void

This method returns an undefined value.

RBS:

  • (ValidationSupport::json_value state, String path) -> void

Parameters:

  • state (ValidationSupport::json_value)
  • path (String)


232
233
234
235
# File 'lib/ibex/table_artifact/validator/tables.rb', line 232

def validate_state(state, path)
  state = integer(state, path, minimum: 0)
  invalid(path, "references a missing state") unless state.between?(0, @state_count - 1)
end

#validate_token_action(code, token_id, path) ⇒ void

This method returns an undefined value.

RBS:

  • (ValidationSupport::json_value code, Integer token_id, String path) -> void

Parameters:

  • code (ValidationSupport::json_value)
  • token_id (Integer)
  • path (String)


214
215
216
217
218
219
# File 'lib/ibex/table_artifact/validator/tables.rb', line 214

def validate_token_action(code, token_id, path)
  code = integer(code, path)
  invalid(path, "accept is only valid for $eof") if code.zero? && !token_id.zero?
  invalid(path, "$eof cannot be shifted") if token_id.zero? && code.positive?
  validate_action_code(code, path)
end