// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers // SPDX-FileCopyrightText: 2026 The Project Pterodactyl Developers // // SPDX-License-Identifier: MPL-2.0 public class SyntaxTreeBuilder { private enum Event: Equatable { case open case close(kind: SyntaxTreeKind, metadata: SyntaxTreeMetadata?, cancelled: Bool) case advance(token: Token, metadata: TokenMetadata?) } private var events: [Event] = [] public init() {} public struct SubtreeHandle: ~Copyable { let parent: SyntaxTreeBuilder var kind: SyntaxTreeKind var metadata: SyntaxTreeMetadata? private var closed = false var cancelled = false init(parent: SyntaxTreeBuilder, kind: SyntaxTreeKind? = nil, metadata: SyntaxTreeMetadata? = nil) { self.parent = parent self.kind = kind ?? .error self.metadata = metadata parent.events.append(.open) } mutating func cancel() { cancelled = true } mutating func close() { precondition(!closed) parent.events.append(.close(kind: kind, metadata: metadata, cancelled: cancelled)) closed = true } deinit { precondition(closed) } } public func open(kind: SyntaxTreeKind? = nil, metadata: SyntaxTreeMetadata? = nil) -> SubtreeHandle { SubtreeHandle(parent: self, kind: kind, metadata: metadata) } public func advance(token: Token, metadata: TokenMetadata?) { events.append(.advance(token: token, metadata: metadata)) } public var tree: SyntaxTree { var stack: [SyntaxTree.MutableTree] = [] for event in events { switch event { case .open: stack.append(SyntaxTree.MutableTree(kind: .error, metadata: nil, children: [])) case .close(let kind, let metadata, let cancelled): guard var node = stack.popLast() else { fatalError("Unbalanced tree") } node.kind = kind node.metadata = metadata guard let parentIndex = stack.indices.last else { return node.tree } if cancelled { stack[parentIndex].children.append(contentsOf: node.children) } else { stack[parentIndex].children.append(.tree(node.tree)) } case .advance(let token, let metadata): guard let parentIndex = stack.indices.last else { fatalError("Attempted to insert token at root of parse tree") } stack[parentIndex] .children .append(.token(token, metadata: metadata)) } } fatalError("Unbalanced syntax tree") } } extension Array { fileprivate mutating func modifyLast(_ modifier: (inout Element) -> Void) { if var last = popLast() { modifier(&last) append(last) } } }