this repo has no description
www.jonmsterling.com/01HC/
1// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
2//
3// SPDX-License-Identifier: MPL-2.0
4
5public struct Parser {
6 public struct MarkOpened {
7 internal let index: Int
8 }
9
10 let source: String
11 let tokens: [Token]
12
13 public init(source: String, tokens: [Token]) {
14 self.source = source
15 self.tokens = tokens
16 }
17
18 public private(set) var diagnostics: [Diagnostic] = []
19 public var builder: SyntaxTreeBuilder = SyntaxTreeBuilder()
20
21 private var inError: Bool = false
22 private var position: Int = 0
23 private var absoluteUtf16Offset: Int = 0
24 public var absoluteRangeAtCursor: Range<Int> {
25 return absoluteUtf16Offset..<absoluteUtf16Offset
26 }
27
28 public var absoluteRangeOfCurrentToken: Range<Int> {
29 return absoluteUtf16Offset..<absoluteUtf16Offset + currentToken.utf16Length
30 }
31
32 public var isEndOfFile: Bool {
33 position == tokens.count
34 }
35
36 public var currentToken: Token {
37 if tokens.indices.contains(position) {
38 return tokens[position]
39 } else {
40 return Token(
41 kind: .eof,
42 text: "",
43 )
44 }
45 }
46
47 public func isAt(kind: TokenKind) -> Bool {
48 currentToken.kind == kind
49 }
50
51 public func isAt(kindSatisfying predicate: (TokenKind) -> Bool) -> Bool {
52 return predicate(currentToken.kind)
53 }
54
55
56 public mutating func advance(metadata: TokenMetadata?) {
57 precondition(!isEndOfFile)
58 builder.advance(token: currentToken, metadata: metadata)
59 absoluteUtf16Offset += currentToken.utf16Length
60 position += 1
61 }
62
63 public mutating func advance(error: String, metadata: TokenMetadata? = nil) {
64 let mark = builder.open()
65 let diagnostic = Diagnostic(
66 message: error,
67 absoluteRange: absoluteRangeOfCurrentToken
68 )
69
70 diagnostics.append(diagnostic)
71 advance(metadata: metadata)
72 builder.close(mark: mark, kind: .error, metadata: nil)
73 }
74
75 public mutating func eat(kind: TokenKind, metadata: TokenMetadata?) -> Bool {
76 guard !isEndOfFile && isAt(kindSatisfying: { $0 == kind }) else { return false }
77 advance(metadata: metadata)
78 return true
79 }
80
81 enum ControlFlow {
82 case `continue`
83 case `break`
84 }
85
86 mutating func ate(kind: TokenKind, metadata: TokenMetadata?) -> ControlFlow {
87 guard eat(kind: kind, metadata: metadata) else { return .continue }
88 inError = false
89 eatTrivia()
90 return .break
91 }
92
93 mutating func recoverUntil(_ anchors: Set<TokenKind>, expected: TokenKind, error: String? = nil) {
94 var discardTokens: [Token] = []
95 let startOffset = absoluteUtf16Offset
96
97 while !self.isAt(kindSatisfying: { anchors.contains($0) }) {
98 if isEndOfFile { break }
99 let token = currentToken
100 advance(metadata: nil)
101 discardTokens.append(token)
102 }
103
104 var endOffset = startOffset
105
106 let error = error ?? "Expected \(expected) but got \(discardTokens)"
107
108 if discardTokens.isEmpty {
109 if !inError {
110 inError = true
111 diagnostics.append(Diagnostic(message: error, absoluteRange: absoluteRangeAtCursor))
112 }
113 return
114 } else {
115 let mark = builder.open()
116 for discardToken in discardTokens {
117 endOffset += discardToken.utf16Length
118 }
119
120 builder.close(mark: mark, kind: .error, metadata: nil)
121
122 if !inError {
123 inError = true
124 diagnostics.append(Diagnostic(message: error, absoluteRange: startOffset..<endOffset))
125 }
126 }
127 }
128
129 public mutating func expect(kind: TokenKind, metadata: TokenMetadata?, recovery: Set<TokenKind>, error: String? = nil) {
130 var anchors = recovery
131 if ate(kind: kind, metadata: metadata) == .break { return }
132 anchors.insert(kind)
133 recoverUntil(anchors, expected: kind, error: error)
134 let _ = ate(kind: kind, metadata: metadata)
135 }
136}
137
138extension Parser {
139 mutating func eatTrivium() -> Bool {
140 switch currentToken.kind {
141 case .whitespace, .newline:
142 advance(metadata: nil)
143 return true
144 case .blockComment(let terminated):
145 let metadata = TokenMetadata(
146 semanticTokenType: .comment,
147 delimitedFoldingRangeKind: .comment
148 )
149 if terminated {
150 advance(metadata: metadata)
151 } else {
152 advance(error: "Block comment was not terminated")
153 }
154 return true
155 case .lineComment:
156 advance(metadata: TokenMetadata(semanticTokenType: .comment))
157 return true
158 default:
159 return false
160 }
161 }
162
163 mutating func eatTrivia() {
164 while !isEndOfFile && eatTrivium() {}
165 }
166}