Module: TreeHaver

Defined in:
lib/tree_haver.rb,
lib/tree_haver/version.rb,
lib/tree_haver/contracts.rb,
lib/tree_haver/peg_backends.rb,
lib/tree_haver/language_pack.rb,
lib/tree_haver/kaitai_backend.rb,
lib/tree_haver/backend_context.rb,
lib/tree_haver/backend_registry.rb

Defined Under Namespace

Modules: BackendRegistry, Version Classes: AdapterInfo, BackendReference, BinaryDiagnostic, BinaryMergeReport, BinaryNestedDispatch, BinaryPayloadRegion, BinaryRawPayload, BinaryRenderPolicy, BinaryScalarValue, ByteEditSpan, ByteRange, FeatureProfile, KaitaiByteSpan, KaitaiTreeAnalysis, KaitaiTreeNode, LanguagePackAnalysis, LanguagePackProcessAnalysis, ParserDiagnostics, ParserRequest, ProcessDiagnostic, ProcessImportInfo, ProcessRequest, ProcessSpan, ProcessStructureItem, SourcePoint, SourceSpan, ZipArchiveEntry, ZipArchiveInfo, ZipFamilyReport, ZipMemberDecision, ZipUnsafeEntry

Constant Summary collapse

PACKAGE_NAME =
"tree_haver"
VERSION =
Version::VERSION
CITRUS_BACKEND =
BackendReference.new(
  id: "citrus",
  family: "peg"
).freeze
PARSLET_BACKEND =
BackendReference.new(
  id: "parslet",
  family: "peg"
).freeze
KREUZBERG_LANGUAGE_PACK_BACKEND =
BackendReference.new(
  id: "kreuzberg-language-pack",
  family: "tree-sitter"
).freeze
KAITAI_STRUCT_BACKEND =
BackendReference.new(
  id: "kaitai-struct",
  family: "kaitai"
).freeze
BACKEND_CONTEXT_KEY =
:structured_merge_tree_haver_backend

Class Method Summary collapse

Class Method Details

.byte_offset_for_point(source, point) ⇒ Object

Raises:

  • (RangeError)


328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/tree_haver/contracts.rb', line 328

def self.byte_offset_for_point(source, point)
  raise RangeError, "invalid source point (#{point.row}, #{point.column})" if point.row.to_i.negative? || point.column.to_i.negative?

  row = 0
  column = 0
  source.to_s.bytes.each_with_index do |byte, offset|
    return offset if row == point.row.to_i && column == point.column.to_i

    if byte == 10
      row += 1
      column = 0
    else
      column += 1
    end
  end
  return source.to_s.bytesize if row == point.row.to_i && column == point.column.to_i

  raise RangeError, "source point (#{point.row}, #{point.column}) is outside source"
end

.current_backend_idObject



8
9
10
# File 'lib/tree_haver/backend_context.rb', line 8

def current_backend_id
  Thread.current[BACKEND_CONTEXT_KEY]
end

.kaitai_adapter_infoObject



13
14
15
16
17
18
19
20
# File 'lib/tree_haver/kaitai_backend.rb', line 13

def kaitai_adapter_info
  AdapterInfo.new(
    backend: KAITAI_STRUCT_BACKEND.id,
    backend_ref: KAITAI_STRUCT_BACKEND,
    supports_dialects: false,
    supported_policies: []
  )
end

.kaitai_feature_profileObject



22
23
24
25
26
27
28
29
# File 'lib/tree_haver/kaitai_backend.rb', line 22

def kaitai_feature_profile
  FeatureProfile.new(
    backend: KAITAI_STRUCT_BACKEND.id,
    backend_ref: KAITAI_STRUCT_BACKEND,
    supports_dialects: false,
    supported_policies: []
  )
end

.language_pack_adapter_infoObject



16
17
18
19
20
21
22
23
# File 'lib/tree_haver/language_pack.rb', line 16

def language_pack_adapter_info
  AdapterInfo.new(
    backend: KREUZBERG_LANGUAGE_PACK_BACKEND.id,
    backend_ref: KREUZBERG_LANGUAGE_PACK_BACKEND,
    supports_dialects: false,
    supported_policies: []
  )
end

.language_pack_feature_profileObject



25
26
27
28
29
30
31
32
# File 'lib/tree_haver/language_pack.rb', line 25

def language_pack_feature_profile
  FeatureProfile.new(
    backend: KREUZBERG_LANGUAGE_PACK_BACKEND.id,
    backend_ref: KREUZBERG_LANGUAGE_PACK_BACKEND,
    supports_dialects: false,
    supported_policies: []
  )
end

.parse_with_citrus(source, grammar_module:) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/tree_haver/peg_backends.rb', line 37

def parse_with_citrus(source, grammar_module:)
  raw = grammar_module.parse(source)
  if raw&.respond_to?(:captures)
    {
      ok: true,
      backend_ref: CITRUS_BACKEND,
      raw: raw,
      diagnostics: []
    }
  else
    {
      ok: false,
      backend_ref: CITRUS_BACKEND,
      diagnostics: [{ severity: "error", category: "parse_error", message: "Citrus parse failed." }]
    }
  end
rescue StandardError => e
  {
    ok: false,
    backend_ref: CITRUS_BACKEND,
    diagnostics: [{ severity: "error", category: "parse_error", message: e.message }]
  }
end

.parse_with_language_pack(request) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/tree_haver/language_pack.rb', line 34

def parse_with_language_pack(request)
  ensure_language_pack_language(request.language)
  raw = TreeSitterLanguagePack.process(
    request.source,
    JSON.generate(language: request.language, diagnostics: true)
  )
  diagnostics = Array(raw["diagnostics"])
  return parse_error_result(request.language) unless diagnostics.empty?

  analysis = LanguagePackAnalysis.new(
    language: request.language,
    dialect: request.dialect,
    root_type: inferred_root_type(request),
    has_error: false,
    backend_ref: KREUZBERG_LANGUAGE_PACK_BACKEND
  )
  parse_result(ok: true, analysis: analysis, diagnostics: [])
rescue StandardError => e
  parse_result(
    ok: false,
    diagnostics: [diagnostic("error", "unsupported_feature", e.message)]
  )
end

.parse_with_parslet(source, grammar_class:) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/tree_haver/peg_backends.rb', line 61

def parse_with_parslet(source, grammar_class:)
  raw = grammar_class.new.parse(source)
  {
    ok: true,
    backend_ref: PARSLET_BACKEND,
    raw: raw,
    diagnostics: []
  }
rescue StandardError => e
  {
    ok: false,
    backend_ref: PARSLET_BACKEND,
    diagnostics: [{ severity: "error", category: "parse_error", message: e.message }]
  }
end

.peg_adapter_info(backend_ref) ⇒ Object



19
20
21
22
23
24
25
26
# File 'lib/tree_haver/peg_backends.rb', line 19

def peg_adapter_info(backend_ref)
  AdapterInfo.new(
    backend: backend_ref.id,
    backend_ref: backend_ref,
    supports_dialects: false,
    supported_policies: []
  )
end

.peg_feature_profile(backend_ref) ⇒ Object



28
29
30
31
32
33
34
35
# File 'lib/tree_haver/peg_backends.rb', line 28

def peg_feature_profile(backend_ref)
  FeatureProfile.new(
    backend: backend_ref.id,
    backend_ref: backend_ref,
    supports_dialects: false,
    supported_policies: []
  )
end

.process_with_language_pack(request) ⇒ Object



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/tree_haver/language_pack.rb', line 58

def process_with_language_pack(request)
  ensure_language_pack_language(request.language)
  raw = TreeSitterLanguagePack.process(
    request.source,
    JSON.generate(language: request.language, structure: true, imports: true, diagnostics: true)
  )
  analysis = LanguagePackProcessAnalysis.new(
    language: raw.fetch("language"),
    structure: Array(raw["structure"]).map do |item|
      ProcessStructureItem.new(
        kind: item.fetch("kind").downcase,
        name: item["name"],
        span: process_span(item.fetch("span"))
      )
    end,
    imports: normalize_imports(request.language, Array(raw["imports"])),
    diagnostics: Array(raw["diagnostics"]).map do |item|
      ProcessDiagnostic.new(
        message: item.fetch("message"),
        severity: item.fetch("severity")
      )
    end,
    backend_ref: KREUZBERG_LANGUAGE_PACK_BACKEND
  )
  parse_result(ok: true, analysis: analysis, diagnostics: [])
rescue StandardError => e
  parse_result(
    ok: false,
    diagnostics: [diagnostic("error", "unsupported_feature", e.message)]
  )
end

.slice_byte_range(source, byte_range) ⇒ Object



319
320
321
322
323
324
325
326
# File 'lib/tree_haver/contracts.rb', line 319

def self.slice_byte_range(source, byte_range)
  source_bytesize = source.to_s.bytesize
  unless byte_range.valid? && byte_range.end_byte.to_i <= source_bytesize
    raise RangeError, "invalid byte range [#{byte_range.start_byte}, #{byte_range.end_byte}) for source length #{source_bytesize}"
  end

  source.to_s.byteslice(byte_range.start_byte.to_i...byte_range.end_byte.to_i)
end

.with_backend(backend_id) ⇒ Object



12
13
14
15
16
17
18
19
20
# File 'lib/tree_haver/backend_context.rb', line 12

def with_backend(backend_id)
  validate_backend_id!(backend_id)

  previous_backend = Thread.current[BACKEND_CONTEXT_KEY]
  Thread.current[BACKEND_CONTEXT_KEY] = backend_id.to_s
  yield
ensure
  Thread.current[BACKEND_CONTEXT_KEY] = previous_backend
end