Module: Ibex::IR::Serialize
- Defined in:
- lib/ibex/ir/serialize.rb,
sig/ibex/ir/serialize.rbs
Overview
Stable JSON serialization for the current pipeline IR. rubocop:disable Metrics/ModuleLength -- explicit fields keep serialization changes auditable. rubocop:disable Lint/SelfAssignment -- RBS inline assertions narrow schema-decoded values in place.
Class Method Summary collapse
- .dump(value) ⇒ Object
- .empty_chunks ⇒ Object
- .empty_parameters ⇒ Object
- .empty_printers ⇒ Object
- .empty_recovery ⇒ Object
- .empty_tests ⇒ Object
- .load(source) ⇒ Object
- .load_automaton(data) ⇒ Object
-
.load_grammar(data) ⇒ Object
rubocop:disable Metrics/AbcSize -- explicit current Grammar IR field mapping is the loader contract.
- .load_grammar_tests(tests) ⇒ Object
- .load_lexer(data) ⇒ Object
- .load_parser_contract(value) ⇒ Object
- .load_production(production) ⇒ Object
- .load_productions(data) ⇒ Object
- .load_state(state, grammar) ⇒ Object
- .load_symbol_metadata(symbol, field) ⇒ Object
- .load_symbols(data) ⇒ Object
- .load_user_code_chunks(chunks) ⇒ Object
- .normalize_action(value) ⇒ Object
- .symbol_keyed(values, grammar, actions: false) ⇒ Object
- .symbol_source_position(symbol) ⇒ Object
-
.symbolize(value) ⇒ Object
Parsed IR values are recursively heterogeneous until schema validation narrows them.
- .validate_version(data) ⇒ Object
Instance Method Summary collapse
Class Method Details
.dump(value) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/ibex/ir/serialize.rb', line 60 def dump(value) normalize = lambda do |entry| case entry when String normalized = entry.dup.force_encoding(Encoding::UTF_8) normalized.valid_encoding? ? normalized : normalized.scrub when Array entry.map { |child| normalize.call(child) } when Hash entry.to_h { |key, child| [normalize.call(key), normalize.call(child)] } else entry end end "#{JSON.pretty_generate(normalize.call(value.to_h))}\n" end |
.empty_chunks ⇒ Object
150 151 152 153 |
# File 'lib/ibex/ir/serialize.rb', line 150 def empty_chunks value = {} #: Hash[String, IR::serialized_value] value.freeze end |
.empty_parameters ⇒ Object
156 157 158 159 |
# File 'lib/ibex/ir/serialize.rb', line 156 def empty_parameters value = [] #: Array[IR::serialized_value] value.freeze end |
.empty_printers ⇒ Object
162 163 164 165 |
# File 'lib/ibex/ir/serialize.rb', line 162 def empty_printers value = [] #: Array[IR::serialized_value] value.freeze end |
.empty_recovery ⇒ Object
174 175 176 177 |
# File 'lib/ibex/ir/serialize.rb', line 174 def empty_recovery value = { "sync_tokens" => [], "on_error_reduce" => [] } #: Hash[String, IR::serialized_value] value.freeze end |
.empty_tests ⇒ Object
168 169 170 171 |
# File 'lib/ibex/ir/serialize.rb', line 168 def empty_tests value = [] #: Array[IR::serialized_value] value.freeze end |
.load(source) ⇒ Object
79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/ibex/ir/serialize.rb', line 79 def load(source) data = JSON.parse(source) #: Hash[String, IR::serialized_value] type = data.fetch("ibex_ir") #: String return load_lexer(data) if type == "lexer" validate_version(data) return load_grammar(data) if type == "grammar" return load_automaton(data) if type == "automaton" raise Ibex::Error, "(ir):1:1: unsupported IR type #{type.inspect}" rescue JSON::ParserError => e raise Ibex::Error, "(ir):1:1: invalid JSON: #{e.}" end |
.load_automaton(data) ⇒ Object
180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/ibex/ir/serialize.rb', line 180 def load_automaton(data) data = data #: Hash[String, IR::serialized_value] grammar_data = data.fetch("grammar") #: Hash[String, IR::serialized_value] grammar = load_grammar(grammar_data) states_data = data.fetch("states") #: Array[Hash[String, IR::serialized_value]] states = states_data.map { |state| load_state(state, grammar) } data.fetch("schema_version") #: Integer Automaton.new( grammar: grammar, states: states, conflict_summary: symbolize(data.fetch("conflict_summary")), algorithm: data.fetch("algorithm"), grammar_digest: data.fetch("grammar_digest"), entry_states: data["entry_states"], entry_construction: data.fetch("entry_construction") ) end |
.load_grammar(data) ⇒ Object
rubocop:disable Metrics/AbcSize -- explicit current Grammar IR field mapping is the loader contract.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/ibex/ir/serialize.rb', line 108 def load_grammar(data) data = data #: Hash[String, IR::serialized_value] data.fetch("schema_version") #: Integer Grammar.new( class_name: data.fetch("class_name"), superclass: data["superclass"], start: data.fetch("start"), expect: data.fetch("expect"), options: symbolize(data.fetch("options")), symbols: load_symbols(data), mode: (data["mode"] || "default").to_sym, starts: data["starts"], expect_rr: data["expect_rr"], parser_parameters: symbolize(data.fetch("params", empty_parameters)), value_printers: symbolize(data.fetch("printers", empty_printers)), grammar_tests: load_grammar_tests(data.fetch("tests", empty_tests)), lexer: data["lexer"] && load_lexer(data.fetch("lexer")), recovery: symbolize(data.fetch("recovery", empty_recovery)), productions: load_productions(data), user_code: data.fetch("user_code"), conversions: data.fetch("conversions"), warnings: symbolize(data.fetch("warnings")), user_code_chunks: load_user_code_chunks(data.fetch("user_code_chunks", empty_chunks)), source_provenance: symbolize(data["source_provenance"]), parser_contract: load_parser_contract(data.fetch("parser_contract")) ) end |
.load_grammar_tests(tests) ⇒ Object
315 316 317 318 |
# File 'lib/ibex/ir/serialize.rb', line 315 def load_grammar_tests(tests) tests = tests #: Array[IR::serialized_value] symbolize(tests).map { |test| test.merge(expectation: test.fetch(:expectation).to_sym) } end |
.load_lexer(data) ⇒ Object
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/ibex/ir/serialize.rb', line 195 def load_lexer(data) data = data #: Hash[String, IR::serialized_value] version = data.fetch("schema_version") #: Integer unless SUPPORTED_LEXER_SCHEMA_VERSIONS.include?(version) expected = SUPPORTED_LEXER_SCHEMA_VERSIONS.join(", ") raise Ibex::Error, "(ir):1:1: unsupported lexer schema_version #{version.inspect}; " \ "expected the current lexer format (#{expected})" end rules_data = data.fetch("rules") #: Array[Hash[String, IR::serialized_value]] rules = rules_data.map do |rule| LexerRule.new( id: rule.fetch("id"), state: rule.fetch("state"), kind: rule.fetch("kind").to_sym, token: rule["token"], pattern: rule.fetch("pattern"), pattern_kind: rule.fetch("pattern_kind").to_sym, options: rule.fetch("options"), action: rule["action"], location: symbolize(rule.fetch("loc")) ) end Lexer.new( states: data.fetch("states"), rules: rules, warnings: symbolize(data.fetch("warnings")), schema_version: version, source_provenance: symbolize(data["source_provenance"]) ) end |
.load_parser_contract(value) ⇒ Object
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/ibex/ir/serialize.rb', line 321 def load_parser_contract(value) value = value #: Hash[String, IR::serialized_value] entries = ParserContract::DEFINITIONS.keys.to_h do |key| entry = value.fetch(key.to_s) location = entry["loc"] loaded_location = if location Location.new( file: location.fetch("file"), line: location.fetch("line"), column: location.fetch("column") ) end [ key, ParserContract::Entry.new( key, value: entry["value"]&.to_sym, explicit: entry.fetch("explicit"), location: loaded_location ) ] end ParserContract.new(**entries) end |
.load_production(production) ⇒ Object
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/ibex/ir/serialize.rb', line 258 def load_production(production) production = production #: Hash[String, IR::serialized_value] action_data = production["action"] action_data = action_data #: Hash[String, IR::serialized_value] if action_data action = if action_data Action.new(code: action_data.fetch("code"), location: symbolize(action_data["loc"]), named_refs: symbolize(action_data.fetch("named_refs")), context_length: action_data.fetch("context_length"), composition: symbolize(action_data["composition"])) end Production.new(id: production.fetch("id"), lhs: production.fetch("lhs"), rhs: production.fetch("rhs"), action: action, precedence_override: production["prec_override"], origin: symbolize(production.fetch("origin")), documentation: production["doc"], expansion: symbolize(production["expansion"]), node: symbolize(production["node"])) end |
.load_productions(data) ⇒ Object
144 145 146 147 |
# File 'lib/ibex/ir/serialize.rb', line 144 def load_productions(data) productions_data = data.fetch("productions") #: Array[Hash[String, IR::serialized_value]] productions_data.map { |production| load_production(production) } end |
.load_state(state, grammar) ⇒ Object
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/ibex/ir/serialize.rb', line 219 def load_state(state, grammar) state = state #: Hash[String, IR::serialized_value] items_data = state.fetch("items") #: Array[Hash[String, IR::serialized_value]] items = items_data.map do |item| lookahead_names = item.fetch("lookaheads") #: Array[String] lookaheads = lookahead_names.map do |name| symbol = grammar.symbol(name) || raise(Ibex::Error, "(ir):1:1: unknown symbol #{name.inspect}") symbol.id end AutomatonItem.new(production: item.fetch("production"), dot: item.fetch("dot"), lookaheads: lookaheads) end AutomatonState.new(id: state.fetch("id"), items: items, transitions: symbol_keyed(state.fetch("transitions"), grammar), actions: symbol_keyed(state.fetch("actions"), grammar, actions: true), gotos: symbol_keyed(state.fetch("gotos"), grammar), default_action: normalize_action(state["default_action"]), conflicts: symbolize(state.fetch("conflicts"))) end |
.load_symbol_metadata(symbol, field) ⇒ Object
286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/ibex/ir/serialize.rb', line 286 def (symbol, field) symbol = symbol #: Hash[String, IR::serialized_value] value = symbol[field] return nil if value.nil? position = symbol_source_position(symbol) raise Ibex::Error, "#{position}: #{field} must be a String or null" unless value.is_a?(String) raise Ibex::Error, "#{position}: #{field} must not be empty" if value.strip.empty? raise Ibex::Error, "#{position}: #{field} must be a single line" if value.match?(/[\r\n]/) raise Ibex::Error, "#{position}: #{field} must not contain control characters" if value.match?(/[[:cntrl:]]/) value end |
.load_symbols(data) ⇒ Object
131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/ibex/ir/serialize.rb', line 131 def load_symbols(data) symbols_data = data.fetch("symbols") #: Array[Hash[String, IR::serialized_value]] symbols_data.map do |symbol| GrammarSymbol.new(id: symbol.fetch("id"), name: symbol.fetch("name"), kind: symbol.fetch("kind"), reserved: symbol.fetch("reserved"), precedence: symbolize(symbol["prec"]), location: symbolize(symbol["loc"]), display_name: (symbol, "display_name"), semantic_type: (symbol, "semantic_type"), documentation: symbol["doc"]) end end |
.load_user_code_chunks(chunks) ⇒ Object
275 276 277 278 279 280 281 282 283 |
# File 'lib/ibex/ir/serialize.rb', line 275 def load_user_code_chunks(chunks) chunks = chunks #: Hash[String, Array[Hash[String, IR::serialized_value]]] chunks.to_h do |name, values| loaded = values.map do |value| UserCodeChunk.new(code: value.fetch("code"), location: symbolize(value.fetch("loc"))) end [name, loaded] end end |
.normalize_action(value) ⇒ Object
248 249 250 251 252 253 254 255 |
# File 'lib/ibex/ir/serialize.rb', line 248 def normalize_action(value) value = value #: IR::serialized_value return nil unless value action = symbolize(value) action[:type] = action[:type].to_sym action end |
.symbol_keyed(values, grammar, actions: false) ⇒ Object
239 240 241 242 243 244 245 |
# File 'lib/ibex/ir/serialize.rb', line 239 def symbol_keyed(values, grammar, actions: false) values = values #: Hash[String, IR::serialized_value] values.to_h do |name, value| symbol = grammar.symbol(name) || raise(Ibex::Error, "(ir):1:1: unknown symbol #{name.inspect}") [symbol.id, actions ? normalize_action(value) : value] end end |
.symbol_source_position(symbol) ⇒ Object
302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/ibex/ir/serialize.rb', line 302 def symbol_source_position(symbol) location = symbol["loc"] return "(ir):1:1" unless location.is_a?(Hash) file = location["file"] line = location["line"] column = location["column"] return "(ir):1:1" unless file.is_a?(String) && line.is_a?(Integer) && column.is_a?(Integer) "#{file}:#{line}:#{column}" end |
.symbolize(value) ⇒ Object
Parsed IR values are recursively heterogeneous until schema validation narrows them.
345 346 347 348 349 350 351 352 |
# File 'lib/ibex/ir/serialize.rb', line 345 def symbolize(value) value = value #: IR::serialized_value case value when Array then value.map { |item| symbolize(item) } when Hash then value.to_h { |key, item| [key.to_sym, symbolize(item)] } else value end end |
.validate_version(data) ⇒ Object
97 98 99 100 101 102 103 104 |
# File 'lib/ibex/ir/serialize.rb', line 97 def validate_version(data) version = data["schema_version"] return if SUPPORTED_SCHEMA_VERSIONS.include?(version) expected = SUPPORTED_SCHEMA_VERSIONS.join(", ") raise Ibex::Error, "(ir):1:1: unsupported schema_version #{version.inspect}; expected the current format (#{expected})" end |
Instance Method Details
#self?.dump ⇒ String
94 |
# File 'sig/ibex/ir/serialize.rbs', line 94
def self?.dump: (Grammar | Automaton | Lexer value) -> String
|
#self?.load ⇒ Grammar, ...
97 |
# File 'sig/ibex/ir/serialize.rbs', line 97
def self?.load: (String source) -> (Grammar | Automaton | Lexer)
|