// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers // // SPDX-License-Identifier: MPL-2.0 public struct Parser { public struct MarkOpened { internal let index: Int } let source: String let tokens: [Token] public init(source: String, tokens: [Token]) { self.source = source self.tokens = tokens } public private(set) var diagnostics: [Diagnostic] = [] public var builder: SyntaxTreeBuilder = SyntaxTreeBuilder() private var inError: Bool = false private var position: Int = 0 private var absoluteUtf16Offset: Int = 0 public var absoluteRangeAtCursor: Range { return absoluteUtf16Offset.. { return absoluteUtf16Offset.. Bool { currentToken.kind == kind } public func isAt(kindSatisfying predicate: (TokenKind) -> Bool) -> Bool { return predicate(currentToken.kind) } public mutating func advance(metadata: TokenMetadata?) { precondition(!isEndOfFile) builder.advance(token: currentToken, metadata: metadata) absoluteUtf16Offset += currentToken.utf16Length position += 1 } public mutating func advance(error: String, metadata: TokenMetadata? = nil) { let mark = builder.open() let diagnostic = Diagnostic( message: error, absoluteRange: absoluteRangeOfCurrentToken ) diagnostics.append(diagnostic) advance(metadata: metadata) builder.close(mark: mark, kind: .error, metadata: nil) } public mutating func eat(kind: TokenKind, metadata: TokenMetadata?) -> Bool { guard !isEndOfFile && isAt(kindSatisfying: { $0 == kind }) else { return false } advance(metadata: metadata) return true } enum ControlFlow { case `continue` case `break` } mutating func ate(kind: TokenKind, metadata: TokenMetadata?) -> ControlFlow { guard eat(kind: kind, metadata: metadata) else { return .continue } inError = false eatTrivia() return .break } mutating func recoverUntil(_ anchors: Set, expected: TokenKind, error: String? = nil) { var discardTokens: [Token] = [] let startOffset = absoluteUtf16Offset while !self.isAt(kindSatisfying: { anchors.contains($0) }) { if isEndOfFile { break } let token = currentToken advance(metadata: nil) discardTokens.append(token) } var endOffset = startOffset let error = error ?? "Expected \(expected) but got \(discardTokens)" if discardTokens.isEmpty { if !inError { inError = true diagnostics.append(Diagnostic(message: error, absoluteRange: absoluteRangeAtCursor)) } return } else { let mark = builder.open() for discardToken in discardTokens { endOffset += discardToken.utf16Length } builder.close(mark: mark, kind: .error, metadata: nil) if !inError { inError = true diagnostics.append(Diagnostic(message: error, absoluteRange: startOffset.., error: String? = nil) { var anchors = recovery if ate(kind: kind, metadata: metadata) == .break { return } anchors.insert(kind) recoverUntil(anchors, expected: kind, error: error) let _ = ate(kind: kind, metadata: metadata) } } extension Parser { mutating func eatTrivium() -> Bool { switch currentToken.kind { case .whitespace, .newline: advance(metadata: nil) return true case .blockComment(let terminated): let metadata = TokenMetadata( semanticTokenType: .comment, delimitedFoldingRangeKind: .comment ) if terminated { advance(metadata: metadata) } else { advance(error: "Block comment was not terminated") } return true case .lineComment: advance(metadata: TokenMetadata(semanticTokenType: .comment)) return true default: return false } } mutating func eatTrivia() { while !isEndOfFile && eatTrivium() {} } }