···
1
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
3
+
// SPDX-License-Identifier: MPL-2.0
5
+
public struct Parser {
6
+
enum Event: Equatable {
7
+
case open(kind: SyntaxTreeKind)
12
+
public struct MarkOpened {
13
+
internal let index: Int
19
+
public init(source: String, tokens: [Token]) {
20
+
self.source = source
21
+
self.tokens = tokens
24
+
public private(set) var diagnostics: [Diagnostic] = []
26
+
private var fuel: Int = 0
27
+
private var position: Int = 0
28
+
private var events: [Event] = []
29
+
private var absoluteUtf16Offset: Int = 0
30
+
public var absoluteRangeAtCursor: Range<Int> {
31
+
return absoluteUtf16Offset..<absoluteUtf16Offset
34
+
public var absoluteRangeOfCurrentToken: Range<Int> {
35
+
return absoluteUtf16Offset..<absoluteUtf16Offset + currentToken.utf16Length
38
+
public var isEndOfFile: Bool {
39
+
position == tokens.count
42
+
public var currentToken: Token {
43
+
if tokens.indices.contains(position) {
44
+
return tokens[position]
53
+
public func isAt(kind: TokenKind) -> Bool {
54
+
currentToken.kind == kind
57
+
public func isAt(kindSatisfying predicate: (TokenKind) -> Bool) -> Bool {
58
+
return predicate(currentToken.kind)
62
+
public mutating func open() -> MarkOpened {
63
+
let mark = MarkOpened(index: events.count)
64
+
events.append(.open(kind: .error))
68
+
public mutating func close(mark: MarkOpened, kind: SyntaxTreeKind) {
69
+
events[mark.index] = .open(kind: kind)
70
+
events.append(.close)
73
+
public mutating func advance() {
74
+
precondition(!isEndOfFile)
75
+
events.append(.advance)
76
+
absoluteUtf16Offset += currentToken.utf16Length
81
+
public mutating func advance(error: String?) {
84
+
let diagnostic = Diagnostic(
86
+
absoluteRange: absoluteRangeOfCurrentToken
89
+
diagnostics.append(diagnostic)
92
+
close(mark: mark, kind: .error)
95
+
public mutating func lookahead(_ k: Int) -> TokenKind? {
96
+
precondition(fuel > 0, "Parser is stuck!")
98
+
let index = position + k
99
+
guard tokens.indices.contains(index) else { return nil }
100
+
return tokens[index].kind
103
+
public mutating func eat(kindSatisfying predicate: (TokenKind) -> Bool) -> Bool {
104
+
guard !isEndOfFile && isAt(kindSatisfying: predicate) else { return false }
109
+
public mutating func eat(kind: TokenKind) -> Bool {
113
+
public mutating func expect(kind: TokenKind, error: String? = nil) {
114
+
if eat(kind: kind) { return }
115
+
let diagnostic = Diagnostic(
116
+
message: error ?? "Expected \(kind) but got \(currentToken.kind): `\(currentToken.text)`",
117
+
absoluteRange: absoluteRangeAtCursor
119
+
diagnostics.append(diagnostic)
122
+
public var tree: SyntaxTree {
123
+
var events = events
124
+
var stack: [SyntaxTree] = []
125
+
var cursor: Int = 0
127
+
precondition(events.popLast() == .close)
129
+
for event in events {
131
+
case .open(let kind):
132
+
stack.append(SyntaxTree(kind: kind, children: []))
134
+
let tree = stack.popLast()!
135
+
stack.modifyLast { last in
136
+
last.children.append(.tree(tree))
139
+
let token = tokens[cursor]
141
+
stack.modifyLast { last in
142
+
last.children.append(.token(token))
147
+
assert(stack.count == 1)
148
+
return stack.popLast()!
153
+
fileprivate mutating func modifyLast(_ modifier: (inout Element) -> Void) {
154
+
if var last = popLast() {