1// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
2// SPDX-FileCopyrightText: 2026 The Project Pterodactyl Developers
3//
4// SPDX-License-Identifier: MPL-2.0
5
6public class SyntaxTreeBuilder {
7 private enum Event: Equatable {
8 case open
9 case close(kind: SyntaxTreeKind, metadata: SyntaxTreeMetadata?, cancelled: Bool)
10 case advance(token: Token, metadata: TokenMetadata?)
11 }
12
13 private var events: [Event] = []
14
15 public init() {}
16
17 public struct SubtreeHandle: ~Copyable {
18 let parent: SyntaxTreeBuilder
19 var kind: SyntaxTreeKind
20 var metadata: SyntaxTreeMetadata?
21 private var closed = false
22 var cancelled = false
23
24 init(parent: SyntaxTreeBuilder, kind: SyntaxTreeKind? = nil, metadata: SyntaxTreeMetadata? = nil) {
25 self.parent = parent
26 self.kind = kind ?? .error
27 self.metadata = metadata
28
29 parent.events.append(.open)
30 }
31
32 mutating func cancel() {
33 cancelled = true
34 }
35
36 mutating func close() {
37 precondition(!closed)
38 parent.events.append(.close(kind: kind, metadata: metadata, cancelled: cancelled))
39 closed = true
40 }
41
42 deinit {
43 precondition(closed)
44 }
45 }
46
47 public func open(kind: SyntaxTreeKind? = nil, metadata: SyntaxTreeMetadata? = nil) -> SubtreeHandle {
48 SubtreeHandle(parent: self, kind: kind, metadata: metadata)
49 }
50
51 public func advance(token: Token, metadata: TokenMetadata?) {
52 events.append(.advance(token: token, metadata: metadata))
53 }
54
55 public var tree: SyntaxTree {
56 var stack: [SyntaxTree.MutableTree] = []
57
58 for event in events {
59 switch event {
60 case .open:
61 stack.append(SyntaxTree.MutableTree(kind: .error, metadata: nil, children: []))
62
63 case .close(let kind, let metadata, let cancelled):
64 guard var node = stack.popLast() else { fatalError("Unbalanced tree") }
65 node.kind = kind
66 node.metadata = metadata
67 guard let parentIndex = stack.indices.last else { return node.tree }
68 if cancelled {
69 stack[parentIndex].children.append(contentsOf: node.children)
70 } else {
71 stack[parentIndex].children.append(.tree(node.tree))
72 }
73
74 case .advance(let token, let metadata):
75 guard let parentIndex = stack.indices.last else {
76 fatalError("Attempted to insert token at root of parse tree")
77 }
78 stack[parentIndex]
79 .children
80 .append(.token(token, metadata: metadata))
81 }
82 }
83
84 fatalError("Unbalanced syntax tree")
85 }
86}
87
88
89extension Array {
90 fileprivate mutating func modifyLast(_ modifier: (inout Element) -> Void) {
91 if var last = popLast() {
92 modifier(&last)
93 append(last)
94 }
95 }
96}