Class: Ibex::TableArtifact::Builder

Inherits:
Object
  • Object
show all
Includes:
CSTProjection
Defined in:
lib/ibex/table_artifact/builder.rb,
sig/ibex/table_artifact/builder.rbs

Overview

Projects validated Automaton IR into an action-free executable sidecar.

Constant Summary collapse

REPRESENTATIONS =

Signature:

  • Array[Symbol]

Returns:

  • (Array[Symbol])
%i[plain compact].freeze

Instance Method Summary collapse

Constructor Details

#initialize(automaton, representation: :compact, cst_trivia: nil, omit_action_call: nil) ⇒ Builder

Returns a new instance of Builder.

RBS:

  • (IR::Automaton automaton, ?representation: Symbol | String, ?cst_trivia: Symbol | String?, ?omit_action_call: bool?) -> void

Parameters:

  • automaton (IR::Automaton)
  • representation: (Symbol, String) (defaults to: :compact)
  • cst_trivia: (Symbol, String, nil) (defaults to: nil)
  • omit_action_call: (Boolean, nil) (defaults to: nil)


21
22
23
24
25
26
27
28
29
30
# File 'lib/ibex/table_artifact/builder.rb', line 21

def initialize(automaton, representation: :compact, cst_trivia: nil, omit_action_call: nil)
  @automaton = automaton
  @grammar = automaton.grammar
  @representation = representation.to_sym
  @cst_trivia = effective_cst_trivia(cst_trivia)
  @omit_action_call = omit_action_call.nil? ? @grammar.options.fetch(:omit_action_call) : omit_action_call
  return if REPRESENTATIONS.include?(@representation)

  raise ArgumentError, "representation must be :plain or :compact"
end

Instance Method Details

#action_slot?(production) ⇒ Boolean

RBS:

  • (IR::Production production) -> bool

Parameters:

Returns:

  • (Boolean)


185
186
187
# File 'lib/ibex/table_artifact/builder.rb', line 185

def action_slot?(production)
  !!(production.node || production.action || !@omit_action_call)
end

#action_table(table) ⇒ json_hash

RBS:

  • (action_table table) -> json_hash

Parameters:

Returns:

  • (json_hash)


124
125
126
127
128
129
130
131
132
133
# File 'lib/ibex/table_artifact/builder.rb', line 124

def action_table(table)
  return compact_action_table(table) if table.is_a?(Tables::CompactActions)

  {
    "encoding" => "signed-sparse-rows-v1",
    "rows" => table.map do |row|
      row.sort.map { |token_id, action| { "token_id" => token_id, "code" => Tables::CompactActions.pack(action) } }
    end
  }
end

#automaton_digestString

RBS:

  • () -> String

Returns:

  • (String)


74
75
76
# File 'lib/ibex/table_artifact/builder.rb', line 74

def automaton_digest
  "sha256:#{Digest::SHA256.hexdigest(IR::Serialize.dump(@automaton))}"
end

#buildDocument

RBS:

  • () -> Document

Returns:



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/ibex/table_artifact/builder.rb', line 33

def build
  source_identity = {
    "grammar_digest" => @automaton.grammar_digest,
    "automaton_digest" => automaton_digest
  }
  payload = build_payload(source_identity)
  cost = build_cost(payload)
  identity = source_identity.merge(
    "payload_digest" => Serializer.digest(payload),
    "cst_metadata_digest" => Serializer.digest(payload.fetch("cst")),
    "recovery_metadata_digest" => Serializer.digest(payload.fetch("recovery"))
  )
  Document.new(
    "artifact_type" => ARTIFACT_TYPE,
    "schema_version" => SCHEMA_VERSION,
    "identity" => identity,
    "payload" => payload,
    "cost" => cost
  )
end

#build_cost(payload) ⇒ json_hash

RBS:

  • (json_hash payload) -> json_hash

Parameters:

  • payload (json_hash)

Returns:

  • (json_hash)


211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/ibex/table_artifact/builder.rb', line 211

def build_cost(payload)
  tables = payload.fetch("tables") #: json_hash
  action_table = tables.fetch("actions") #: json_hash
  goto_table = tables.fetch("gotos") #: json_hash
  {
    "canonical_payload_bytes" => Serializer.compact(payload).bytesize,
    "action_cells" => occupied_cells(action_table, "codes"),
    "goto_cells" => occupied_cells(goto_table, "values"),
    "lookup_cost" => lookup_cost,
    "recognition_cost" => recognition_cost,
    "measurement" => "not-measured",
    "bounded_by_max_steps" => true
  }
end

#build_payload(source_identity) ⇒ json_hash

RBS:

  • (Hash[String, String] source_identity) -> json_hash

Parameters:

  • source_identity (Hash[String, String])

Returns:

  • (json_hash)


57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/ibex/table_artifact/builder.rb', line 57

def build_payload(source_identity)
  {
    "table_format" => table_format,
    "source" => source_identity,
    "state_count" => @automaton.states.length,
    "symbols" => symbols,
    "tokens" => tokens,
    "entry_states" => entry_states,
    "tables" => tables,
    "productions" => productions,
    "cst" => cst,
    "recovery" => recovery,
    "semantic_actions" => semantic_actions
  }
end

#compact_action_table(table) ⇒ json_hash

RBS:

  • (Tables::CompactActions table) -> json_hash

Parameters:

  • table (Tables::CompactActions)

Returns:

  • (json_hash)


136
137
138
139
140
141
142
143
144
145
# File 'lib/ibex/table_artifact/builder.rb', line 136

def compact_action_table(table)
  {
    "encoding" => "signed-row-displacement-v1",
    "row_count" => table.row_count,
    "column_count" => table.column_count,
    "offsets" => table.offsets,
    "codes" => table.codes,
    "checks" => table.checks
  }
end

#compact_goto_table(table) ⇒ json_hash

RBS:

  • (Tables::Compact table) -> json_hash

Parameters:

  • table (Tables::Compact)

Returns:

  • (json_hash)


160
161
162
163
164
165
166
167
168
169
# File 'lib/ibex/table_artifact/builder.rb', line 160

def compact_goto_table(table)
  {
    "encoding" => "row-displacement-v1",
    "row_count" => table.row_count,
    "dense_width" => table.dense_width,
    "offsets" => table.offsets,
    "values" => table.values,
    "checks" => table.checks
  }
end

#effective_cst_trivia(requested) ⇒ Symbol

RBS:

  • (Symbol | String? requested) -> Symbol

Parameters:

  • requested (Symbol, String, nil)

Returns:

  • (Symbol)


255
256
257
258
259
260
261
262
263
# File 'lib/ibex/table_artifact/builder.rb', line 255

def effective_cst_trivia(requested)
  contract_entry = @grammar.parser_contract&.cst_trivia
  persisted = contract_entry.value if contract_entry&.explicit
  if requested && persisted && requested.to_sym != persisted
    raise ArgumentError, "cst_trivia conflicts with the persisted parser contract"
  end

  (requested || persisted || :leading).to_sym
end

#entry_statesArray[json_hash]

RBS:

  • () -> Array[json_hash]

Returns:

  • (Array[json_hash])


109
110
111
# File 'lib/ibex/table_artifact/builder.rb', line 109

def entry_states
  @automaton.entry_states.map { |name, state| { "name" => name, "state" => state } }
end

#goto_table(table) ⇒ json_hash

RBS:

  • (goto_table table) -> json_hash

Parameters:

Returns:

  • (json_hash)


148
149
150
151
152
153
154
155
156
157
# File 'lib/ibex/table_artifact/builder.rb', line 148

def goto_table(table)
  return compact_goto_table(table) if table.is_a?(Tables::Compact)

  {
    "encoding" => "sparse-rows-v1",
    "rows" => table.map do |row|
      row.sort.map { |symbol_id, state| { "symbol_id" => symbol_id, "state" => state } }
    end
  }
end

#lookup_costString

RBS:

  • () -> String

Returns:

  • (String)


238
239
240
241
242
# File 'lib/ibex/table_artifact/builder.rb', line 238

def lookup_cost
  return "O(1) row-displacement probe" if @representation == :compact

  "O(log row width) binary search"
end

#occupied_cells(table, values_key) ⇒ Integer

RBS:

  • (json_hash table, String values_key) -> Integer

Parameters:

  • table (json_hash)
  • values_key (String)

Returns:

  • (Integer)


227
228
229
230
231
232
233
234
235
# File 'lib/ibex/table_artifact/builder.rb', line 227

def occupied_cells(table, values_key)
  if table.key?("rows")
    rows = table.fetch("rows") #: Array[json_value]
    return rows.sum { |row| row.is_a?(Array) ? row.length : 0 }
  end

  values = table.fetch(values_key) #: Array[json_value]
  values.compact.length
end

#productionsArray[json_hash]

RBS:

  • () -> Array[json_hash]

Returns:

  • (Array[json_hash])


172
173
174
175
176
177
178
179
180
181
182
# File 'lib/ibex/table_artifact/builder.rb', line 172

def productions
  @grammar.productions.sort_by(&:id).map do |production|
    {
      "id" => production.id,
      "lhs" => production.lhs,
      "rhs" => production.rhs,
      "rhs_length" => production.rhs.length,
      "action_slot" => action_slot?(production) ? production.id : nil
    }
  end
end

#recognition_costString

RBS:

  • () -> String

Returns:

  • (String)


245
246
247
248
249
250
251
252
# File 'lib/ibex/table_artifact/builder.rb', line 245

def recognition_cost
  prefix = if @representation == :compact
             "O(input tokens + reductions)"
           else
             "O((input tokens + reductions) * log maximum row width)"
           end
  "#{prefix}; lexer, recovery search, and semantic actions excluded"
end

#recoveryjson_hash

RBS:

  • () -> json_hash

Returns:

  • (json_hash)


199
200
201
202
203
204
205
206
207
208
# File 'lib/ibex/table_artifact/builder.rb', line 199

def recovery
  sync_tokens = @grammar.recovery.fetch(:sync_tokens) #: Array[String]
  on_error_reduce = @grammar.recovery.fetch(:on_error_reduce) #: Array[Array[String]]
  {
    "sync_token_ids" => sync_tokens.map { |name| @grammar.symbol(name).id },
    "on_error_reduce_symbol_ids" => on_error_reduce.map do |group|
      group.map { |name| @grammar.symbol(name).id }
    end
  }
end

#semantic_actionsjson_hash

RBS:

  • () -> json_hash

Returns:

  • (json_hash)


190
191
192
193
194
195
196
# File 'lib/ibex/table_artifact/builder.rb', line 190

def semantic_actions
  {
    "binding" => "opaque-wrapper-production-id-v1",
    "verified" => false,
    "slots" => @grammar.productions.select { |production| action_slot?(production) }.map(&:id)
  }
end

#symbolsArray[json_hash]

RBS:

  • () -> Array[json_hash]

Returns:

  • (Array[json_hash])


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

def symbols
  @grammar.symbols.sort_by(&:id).map do |symbol|
    {
      "id" => symbol.id,
      "name" => symbol.name,
      "kind" => symbol.kind.to_s,
      "display_name" => symbol.display_name
    }
  end
end

#table_formatjson_hash

RBS:

  • () -> json_hash

Returns:

  • (json_hash)


79
80
81
82
83
84
85
86
87
# File 'lib/ibex/table_artifact/builder.rb', line 79

def table_format
  {
    "family" => "ibex-runtime-parser-table",
    "version" => Runtime::PARSER_TABLE_FORMAT_VERSION,
    "representation" => @representation.to_s,
    "action_encoding" => @representation == :compact ? "signed-row-displacement-v1" : "signed-sparse-rows-v1",
    "goto_encoding" => @representation == :compact ? "row-displacement-v1" : "sparse-rows-v1"
  }
end

#tablesjson_hash

RBS:

  • () -> json_hash

Returns:

  • (json_hash)


114
115
116
117
118
119
120
121
# File 'lib/ibex/table_artifact/builder.rb', line 114

def tables
  table_set = Tables.build(@automaton, format: @representation) #: Tables::TableSet
  {
    "actions" => action_table(table_set.actions),
    "gotos" => goto_table(table_set.gotos),
    "default_actions" => table_set.default_actions.map { |action| Tables::CompactActions.pack(action) }
  }
end

#tokensArray[json_hash]

RBS:

  • () -> Array[json_hash]

Returns:

  • (Array[json_hash])


102
103
104
105
106
# File 'lib/ibex/table_artifact/builder.rb', line 102

def tokens
  @grammar.terminals.sort_by(&:id).map do |symbol|
    { "id" => symbol.id, "name" => symbol.name, "display_name" => symbol.display_name }
  end
end