Module: Ibex::Runtime::CST::Validator

Defined in:
lib/ibex/runtime/cst/validator.rb

Overview

Validates schema-v1 CST documents while rebuilding all derived Green data.

Constant Summary collapse

TOP_KEYS =

rubocop:disable Metrics/ModuleLength -- closed schema checks remain auditable in one boundary.

%w[
  ibex_ir schema_version grammar_digest table_format state_count production_count trivia_policy kinds root memo
].freeze
KIND_KEYS =

: Array

%w[
  names terminal_range nonterminal_range named named_nonterminals trivia synthetic
].freeze
KNOWN_FLAGS =

: Array

(
  Flags::CONTAINS_ERROR | Flags::CONTAINS_MISSING | Flags::CONTAINS_SKIPPED |
  Flags::HAS_ANNOTATION | Flags::SYNTHETIC | Flags::INCOMPLETE_INPUT
)

Class Method Summary collapse

Class Method Details

.validate(source, grammar_digest: nil, state_count: nil, production_count: nil) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/ibex/runtime/cst/validator.rb', line 41

def validate(source, grammar_digest: nil, state_count: nil, production_count: nil) # rubocop:disable Metrics/MethodLength
  document = JSON.parse(source)
  object!(document, "$")
  exact_keys!(document, TOP_KEYS, "$")
  value!(document, "ibex_ir", "cst", "$.ibex_ir")
  value!(document, "schema_version", 1, "$.schema_version")
  digest = string!(document.fetch("grammar_digest"), "$.grammar_digest")
  unless digest.match?(/\Asha256:[0-9a-f]{64}\z/)
    fail_validation(:invalid_digest, "$.grammar_digest", "expected a SHA-256 digest", actual: digest)
  end
  if grammar_digest && digest != grammar_digest
    fail_validation(
      :grammar_digest_mismatch, "$.grammar_digest",
      "grammar digest does not match", expected: grammar_digest, actual: digest
    )
  end

  kinds = load_kinds(document.fetch("kinds"))
  root = load_element(document.fetch("root"), kinds, "$.root")
  fail_validation(:invalid_root, "$.root", "root must be a node") unless root.is_a?(GreenNode)
  validate_derived!(root, "$.root")
  document_state_count = nonnegative_integer!(document.fetch("state_count"), "$.state_count")
  document_production_count = nonnegative_integer!(
    document.fetch("production_count"), "$.production_count"
  )
  memo = load_memo(
    document.fetch("memo"),
    root,
    digest,
    document_state_count,
    document_production_count,
    expected_state_count: state_count,
    expected_production_count: production_count
  )

  SerializedTree.new(
    grammar_digest: digest,
    table_format: positive_integer!(document.fetch("table_format"), "$.table_format"),
    state_count: document_state_count,
    production_count: document_production_count,
    trivia_policy: trivia_policy!(document.fetch("trivia_policy"), "$.trivia_policy"),
    kinds: kinds, green_root: root, memo: memo
  )
rescue JSON::ParserError => e
  fail_validation(:invalid_json, "$", e.message)
rescue KeyError => e
  fail_validation(:missing_field, "$", e.message)
end