krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Sources/OrgCore/Syntax/GreenTree.swift

92 lines · 2611 bytes

10 symbols in this file
 1public struct GreenToken: Sendable, Equatable {
 2    public let kind: SyntaxKind
 3    public let text: String
 4    /// Length in UTF-16 code units.
 5    public let length: Int
 6
 7    public init(kind: SyntaxKind, text: String) {
 8        self.kind = kind
 9        self.text = text
10        self.length = text.utf16.count
11    }
12}
13
14/// An immutable node. Stores only kind, children and length, so unchanged subtrees can be
15/// shared between versions of a document.
16public final class GreenNode: Sendable, Equatable {
17    public let kind: SyntaxKind
18    public let children: [GreenElement]
19    /// Length in UTF-16 code units.
20    public let length: Int
21
22    public init(kind: SyntaxKind, children: [GreenElement]) {
23        self.kind = kind
24        self.children = children
25        self.length = children.reduce(0) { $0 + $1.length }
26    }
27
28    public static func == (lhs: GreenNode, rhs: GreenNode) -> Bool {
29        lhs === rhs || (lhs.kind == rhs.kind && lhs.children == rhs.children)
30    }
31
32    public var text: String {
33        var out = ""
34        write(to: &out)
35        return out
36    }
37
38    func write(to out: inout String) {
39        for child in children {
40            switch child {
41            case .node(let node): node.write(to: &out)
42            case .token(let token): out += token.text
43            }
44        }
45    }
46}
47
48public enum GreenElement: Sendable, Equatable {
49    case node(GreenNode)
50    case token(GreenToken)
51
52    public var length: Int {
53        switch self {
54        case .node(let node): node.length
55        case .token(let token): token.length
56        }
57    }
58}
59
60struct GreenBuilder {
61    private var stack: [(kind: SyntaxKind, children: [GreenElement])] = []
62    private var root: GreenNode?
63
64    mutating func start(_ kind: SyntaxKind) {
65        stack.append((kind, []))
66    }
67
68    mutating func token(_ kind: SyntaxKind, _ text: some StringProtocol) {
69        guard !text.isEmpty else { return }
70        stack[stack.count - 1].children.append(.token(GreenToken(kind: kind, text: String(text))))
71    }
72
73    /// Appends an already-built node, for reuse across versions.
74    mutating func node(_ green: GreenNode) {
75        stack[stack.count - 1].children.append(.node(green))
76    }
77
78    mutating func finish() {
79        let (kind, children) = stack.removeLast()
80        let node = GreenNode(kind: kind, children: children)
81        if stack.isEmpty {
82            root = node
83        } else {
84            stack[stack.count - 1].children.append(.node(node))
85        }
86    }
87
88    func build() -> GreenNode {
89        precondition(stack.isEmpty, "unfinished nodes")
90        return root!
91    }
92}