aboutsummaryrefslogtreecommitdiff
path: root/Sources/WmapParser/WmapParser.swift
blob: f2bef8feccb77253d3906722240a8ac9d78c7e75 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
import Foundation

// Public API //////////////////////////////////////////////////////////////////

/// Parses a wmap source string.
public func parse(_ source: String) -> Map {
  source.utf8.withContiguousStorageIfAvailable { bytes in
    parseBytes(bytes)
  } ?? parseBytes(Array(source.utf8))
}

// Parsers /////////////////////////////////////////////////////////////////////

struct LineParserContext {
  var components: [Component]
  var dependencies: [Dependency]
  var keywordContext: KeywordParserContext
}

/// Main parsing function, we work on bytes as it's faster.
private func parseBytes<T: Collection>(_ bytes: T) -> Map where T.Element == UInt8, T.Index == Int {
  let estimatedLines = bytes.count / 30

  var context = LineParserContext(
    components: [],
    dependencies: [],
    keywordContext: KeywordParserContext(
      stages: [],
      notes: [],
      groups: [],
      inertias: [],
      evolutions: []
    )
  )

  context.components.reserveCapacity(estimatedLines)
  context.dependencies.reserveCapacity(estimatedLines)
  context.keywordContext.notes.reserveCapacity(estimatedLines / 10)
  context.keywordContext.stages.reserveCapacity(4)
  context.keywordContext.groups.reserveCapacity(estimatedLines / 20)
  context.keywordContext.inertias.reserveCapacity(estimatedLines / 20)
  context.keywordContext.evolutions.reserveCapacity(estimatedLines / 20)

  var index = 0
  let length = bytes.count

  while index < length {
    guard let (start, end) = extractLine(bytes, &index, length) else { continue }
    parseLine(bytes, start, end, &context)
  }

  return Map(
    components: context.components,
    dependencies: context.dependencies,
    notes: context.keywordContext.notes,
    stages: context.keywordContext.stages,
    groups: context.keywordContext.groups,
    inertias: context.keywordContext.inertias,
    evolutions: context.keywordContext.evolutions
  )
}

/// Given a trimmed line, checks if it's a keyword, dependency or component
/// and parses it.
@inline(__always)
private func parseLine<T: Collection>(
  _ bytes: T, _ start: Int, _ end: Int,
  _ context: inout LineParserContext
) where T.Element == UInt8, T.Index == Int {
  if bytes[start] == kOpenSquareBracket {
    parseKeywordLine(bytes, start, end, &context.keywordContext)
  } else if let separatorIndex = hasDependency(bytes, start, end) {
    if let dependency = parseDependency(bytes, start, end, separatorIndex) {
      context.dependencies.append(dependency)
    }
  } else if let component = parseComponent(bytes, start, end) {
    context.components.append(component)
  }
}