1// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers 2// 3// SPDX-License-Identifier: MPL-2.0 4 5public struct Parser { 6 enum Event: Equatable { 7 case open(kind: SyntaxTreeKind, metadata: SyntaxTreeMetadata?) 8 case close 9 case advance(metadata: TokenMetadata?) 10 } 11 12 public struct MarkOpened { 13 internal let index: Int 14 } 15 16 let source: String 17 let tokens: [Token] 18 19 public init(source: String, tokens: [Token]) { 20 self.source = source 21 self.tokens = tokens 22 } 23 24 public private(set) var diagnostics: [Diagnostic] = [] 25 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 32 } 33 34 public var absoluteRangeOfCurrentToken: Range<Int> { 35 return absoluteUtf16Offset..<absoluteUtf16Offset + currentToken.utf16Length 36 } 37 38 public var isEndOfFile: Bool { 39 position == tokens.count 40 } 41 42 public var currentToken: Token { 43 if tokens.indices.contains(position) { 44 return tokens[position] 45 } else { 46 return Token( 47 kind: .eof, 48 text: "", 49 ) 50 } 51 } 52 53 public func isAt(kind: TokenKind) -> Bool { 54 currentToken.kind == kind 55 } 56 57 public func isAt(kindSatisfying predicate: (TokenKind) -> Bool) -> Bool { 58 return predicate(currentToken.kind) 59 } 60 61 62 public mutating func open() -> MarkOpened { 63 let mark = MarkOpened(index: events.count) 64 events.append(.open(kind: .error, metadata: nil)) 65 return mark 66 } 67 68 public mutating func close(mark: MarkOpened, kind: SyntaxTreeKind, metadata: SyntaxTreeMetadata?) { 69 events[mark.index] = .open(kind: kind, metadata: metadata) 70 events.append(.close) 71 } 72 73 public mutating func advance(metadata: TokenMetadata?) { 74 precondition(!isEndOfFile) 75 events.append(.advance(metadata: metadata)) 76 absoluteUtf16Offset += currentToken.utf16Length 77 position += 1 78 fuel = 256 79 } 80 81 public mutating func advance(error: String?, metadata: TokenMetadata? = nil) { 82 let mark = open() 83 if let error { 84 let diagnostic = Diagnostic( 85 message: error, 86 absoluteRange: absoluteRangeOfCurrentToken 87 ) 88 89 diagnostics.append(diagnostic) 90 } 91 advance(metadata: metadata) 92 close(mark: mark, kind: .error, metadata: nil) 93 } 94 95 public mutating func lookahead(_ k: Int) -> TokenKind? { 96 precondition(fuel > 0, "Parser is stuck!") 97 fuel -= 1 98 let index = position + k 99 guard tokens.indices.contains(index) else { return nil } 100 return tokens[index].kind 101 } 102 103 public mutating func eat(kindSatisfying predicate: (TokenKind) -> Bool, metadata: TokenMetadata?) -> Bool { 104 guard !isEndOfFile && isAt(kindSatisfying: predicate) else { return false } 105 advance(metadata: metadata) 106 return true 107 } 108 109 public mutating func eat(kind: TokenKind, metadata: TokenMetadata?) -> Bool { 110 eat(kindSatisfying: { $0 == kind }, metadata: metadata) 111 } 112 113 public mutating func expect(kind: TokenKind, metadata: TokenMetadata?, error: String? = nil) { 114 if eat(kind: kind, metadata: metadata) { return } 115 let diagnostic = Diagnostic( 116 message: error ?? "Expected \(kind) but got \(currentToken.kind): `\(currentToken.text)`", 117 absoluteRange: absoluteRangeAtCursor 118 ) 119 diagnostics.append(diagnostic) 120 } 121 122 public var tree: SyntaxTree { 123 var events = events 124 var stack: [SyntaxTree.Builder] = [] 125 var cursor: Int = 0 126 127 precondition(events.popLast() == .close) 128 129 for event in events { 130 switch event { 131 case .open(let kind, let metadata): 132 stack.append(SyntaxTree.Builder(kind: kind, metadata: metadata, children: [])) 133 case .close: 134 let tree = stack.popLast()! 135 stack.modifyLast { last in 136 last.children.append(.tree(tree.tree)) 137 } 138 case .advance(let metadata): 139 let token = tokens[cursor] 140 cursor += 1 141 stack.modifyLast { last in 142 last.children.append(.token(token, metadata: metadata)) 143 } 144 } 145 } 146 147 assert(stack.count == 1) 148 return stack.popLast()!.tree 149 } 150 151 mutating func eatTrivium() -> Bool { 152 switch currentToken.kind { 153 case .whitespace: 154 advance(metadata: nil) 155 return true 156 case .blockComment(let terminated): 157 let metadata = TokenMetadata( 158 semanticTokenType: .comment, 159 delimitedFoldingRangeKind: .comment 160 ) 161 if terminated { 162 advance(metadata: metadata) 163 } else { 164 advance(error: "Block comment was not terminated") 165 } 166 return true 167 case .lineComment: 168 advance(metadata: TokenMetadata(semanticTokenType: .comment)) 169 return true 170 default: 171 return false 172 } 173 } 174 175 mutating func eatTrivia() { 176 while !isEndOfFile && eatTrivium() {} 177 } 178 179} 180 181extension Array { 182 fileprivate mutating func modifyLast(_ modifier: (inout Element) -> Void) { 183 if var last = popLast() { 184 modifier(&last) 185 append(last) 186 } 187 } 188}