Class: Ibex::Codegen::CSTMetadata

Inherits:
Object
  • Object
show all
Defined in:
lib/ibex/codegen/cst_metadata.rb,
sig/ibex/codegen/cst_metadata.rbs

Overview

Builds the deterministic kind space embedded in CST-aware parser tables.

Constant Summary collapse

TRIVIA_KINDS =

RBS:

  • type kind_map = Hash[String, Integer]
    type kinds = {
      names: Array[String],
      terminal_range: Array[Integer],
      nonterminal_range: Array[Integer],
      named: kind_map,
      named_nonterminals: Hash[Integer, Integer],
      trivia: kind_map,
      synthetic: kind_map
    }
    type field_slot = Integer | { index: Integer, extraction: Symbol }
    type slot = { node_kind: Integer, node_name: String, fields: Hash[String, field_slot] }
    type  = {
      version: Integer,
      trivia_policy: Symbol,
      kinds: kinds,
      slots: Hash[Integer, slot]
    }

Returns:

  • (Array[String])
%w[
  whitespace
  newline
  line_comment
  block_comment
  custom_skip
  skipped_tokens
].freeze
SYNTHETIC_KINDS =

Signature:

  • Array[String]

Returns:

  • (Array[String])
%w[
  lexical_error_token
  missing_token
  error_node
  source_file
  synthetic_root
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(grammar, trivia_policy: :leading) ⇒ CSTMetadata

Returns a new instance of CSTMetadata.

RBS:

  • (IR::Grammar grammar, ?trivia_policy: Symbol | String) -> void

Parameters:

  • grammar (IR::Grammar)
  • trivia_policy: (Symbol, String) (defaults to: :leading)


49
50
51
52
# File 'lib/ibex/codegen/cst_metadata.rb', line 49

def initialize(grammar, trivia_policy: :leading)
  @grammar = grammar
  @trivia_policy = normalize_trivia_policy(trivia_policy)
end

Instance Method Details

#append_kinds(names, additions) ⇒ Hash[String, Integer]

RBS:

  • (Array[String] names, Array[String] additions) -> Hash[String, Integer]

Parameters:

  • names (Array[String])
  • additions (Array[String])

Returns:

  • (Hash[String, Integer])


97
98
99
100
101
102
103
# File 'lib/ibex/codegen/cst_metadata.rb', line 97

def append_kinds(names, additions)
  additions.to_h do |name|
    kind = names.length
    names << name.freeze
    [name.freeze, kind]
  end
end

#append_named_kinds(names) ⇒ Hash[String, Integer]

RBS:

  • (Array[String] names) -> Hash[String, Integer]

Parameters:

  • names (Array[String])

Returns:

  • (Hash[String, Integer])


91
92
93
94
# File 'lib/ibex/codegen/cst_metadata.rb', line 91

def append_named_kinds(names)
  node_names = @grammar.productions.filter_map { |production| production.node&.fetch(:name) }.uniq.sort
  append_kinds(names, node_names)
end

#buildmetadata

Return table-ready CST metadata with stable insertion order.

RBS:

  • () -> metadata

Returns:

  • (metadata)


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

def build
  names = @grammar.symbols.sort_by(&:id).map(&:name)
  named = append_named_kinds(names)
  trivia = append_kinds(names, TRIVIA_KINDS)
  synthetic = append_kinds(names, SYNTHETIC_KINDS)
  terminals = @grammar.terminals.map(&:id)
  nonterminals = @grammar.nonterminals.map(&:id)
  kinds = {
    names: names.freeze,
    terminal_range: half_open_range(terminals),
    nonterminal_range: half_open_range(nonterminals),
    named: named.freeze,
    named_nonterminals: named_nonterminals(named).freeze,
    trivia: trivia.freeze,
    synthetic: synthetic.freeze
  }.freeze #: kinds
   = { # rubocop:disable Style/RedundantAssignment -- preserves the exact record type for Steep.
    version: 1, trivia_policy: @trivia_policy,
    kinds: kinds, slots: (named).freeze
  }.freeze #: metadata
  
end

#field_slot(index, symbol_id) ⇒ field_slot

RBS:

  • (Integer index, Integer symbol_id) -> field_slot

Parameters:

  • index (Integer)
  • symbol_id (Integer)

Returns:



151
152
153
154
155
156
157
# File 'lib/ibex/codegen/cst_metadata.rb', line 151

def field_slot(index, symbol_id)
  extraction = repetition_extraction(symbol_id)
  return index unless extraction

  value = { index: index, extraction: extraction } #: field_slot
  value.freeze
end

#half_open_range(ids) ⇒ Array[Integer]

RBS:

  • (Array[Integer] ids) -> Array[Integer]

Parameters:

  • ids (Array[Integer])

Returns:

  • (Array[Integer])


106
107
108
109
110
# File 'lib/ibex/codegen/cst_metadata.rb', line 106

def half_open_range(ids)
  return [0, 0].freeze if ids.empty?

  [ids.min, ids.max + 1].freeze
end

#named_nonterminals(named) ⇒ Hash[Integer, Integer]

RBS:

  • (Hash[String, Integer] named) -> Hash[Integer, Integer]

Parameters:

  • named (Hash[String, Integer])

Returns:

  • (Hash[Integer, Integer])


113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/ibex/codegen/cst_metadata.rb', line 113

def named_nonterminals(named)
  result = {} #: Hash[Integer, Integer]
  @grammar.productions.each do |production|
    node = production.node
    next unless node

    kind = named.fetch(node.fetch(:name))
    current = result[kind]
    if current && current != production.lhs
      raise ArgumentError, "@node #{node.fetch(:name)} spans multiple nonterminals"
    end

    result[kind] = production.lhs
  end
  result
end

#normalize_trivia_policy(policy) ⇒ Symbol

RBS:

  • (Symbol | String policy) -> Symbol

Parameters:

  • policy (Symbol, String)

Returns:

  • (Symbol)


82
83
84
85
86
87
88
# File 'lib/ibex/codegen/cst_metadata.rb', line 82

def normalize_trivia_policy(policy)
  normalized = policy.to_sym
  normalized = :leading if normalized == :attach
  return normalized if %i[leading balanced drop].include?(normalized)

  raise ArgumentError, "cst_trivia must be :leading, :balanced, or :drop"
end

#repetition_extraction(symbol_id) ⇒ Symbol?

RBS:

  • (Integer symbol_id) -> Symbol?

Parameters:

  • symbol_id (Integer)

Returns:

  • (Symbol, nil)


160
161
162
163
164
165
166
167
168
# File 'lib/ibex/codegen/cst_metadata.rb', line 160

def repetition_extraction(symbol_id)
  origins = @grammar.productions
                    .select { |production| production.lhs == symbol_id }
                    .map { |production| production.origin.fetch(:kind) }.uniq
  return :separated_list if origins == [:separated_list_expansion]
  return :repetition if (origins - %i[star_expansion plus_expansion]).empty? && !origins.empty?

  nil
end

#slot_metadata(named) ⇒ Hash[Integer, slot]

RBS:

  • (Hash[String, Integer] named) -> Hash[Integer, slot]

Parameters:

  • named (Hash[String, Integer])

Returns:

  • (Hash[Integer, slot])


131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/ibex/codegen/cst_metadata.rb', line 131

def (named)
  @grammar.productions.filter_map do |production|
    node = production.node
    next unless node

    fields = node.fetch(:fields).each_with_index.to_h do |name, index|
      [name, field_slot(index, production.rhs.fetch(index))]
    end.freeze
    [
      production.id,
      {
        node_kind: named.fetch(node.fetch(:name)),
        node_name: node.fetch(:name),
        fields: fields
      }.freeze
    ]
  end.to_h
end