Compare changes

Choose any two refs to compare.

+2 -35
Package.swift
···
.library(
name: "PterodactylBuild",
targets: ["PterodactylBuild"]
-
),
-
.executable(
-
name: "PterodactylLanguageServer",
-
targets: ["PterodactylLanguageServer"]
)
],
dependencies: [
-
.package(url: "https://github.com/ChimeHQ/LanguageServer", branch: "main"),
-
.package(url: "https://github.com/ChimeHQ/LanguageServerProtocol", branch: "main"),
-
.package(url: "https://github.com/apple/swift-llbuild2.git", branch: "main"),
-
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"),
-
.package(url: "https://github.com/apple/swift-log", from: "1.6.0")
+
.package(url: "https://github.com/apple/swift-llbuild2.git", branch: "main")
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
···
name: "PterodactylKernel",
),
.target(
-
name: "PterodactylSyntax",
-
dependencies: [
-
.product(name: "Algorithms", package: "swift-algorithms"),
-
"LanguageServerProtocol"
-
]
+
name: "PterodactylSyntax"
),
.target(
name: "PterodactylBuild",
dependencies: [
-
"PterodactylSyntax",
.product(name: "llbuild2fx", package: "swift-llbuild2")
]
),
-
.executableTarget(
-
name: "PterodactylLanguageServer",
-
dependencies: [
-
"LanguageServer",
-
.product(name: "llbuild2fx", package: "swift-llbuild2"),
-
.product(name: "Logging", package: "swift-log"),
-
"PterodactylBuild",
-
"PterodactylSyntax"
-
]
-
),
.testTarget(
name: "PterodactylBuildTests",
dependencies: [
"PterodactylBuild",
-
"PterodactylSyntax",
-
.product(name: "llbuild2fx", package: "swift-llbuild2")
-
]
-
),
-
.testTarget(
-
name: "PterodactylLanguageServerTests",
-
dependencies: [
-
"PterodactylBuild",
-
"PterodactylSyntax",
-
"PterodactylLanguageServer",
.product(name: "llbuild2fx", package: "swift-llbuild2")
]
),
+9 -9
Sources/PterodactylBuild/BuildContext.swift
···
}
extension BuildKey {
-
public func computeValue(_ fi: FXFunctionInterface<Self>, _ ctx: Context) async throws -> ValueType {
+
func computeValue(_ fi: FXFunctionInterface<Self>, _ ctx: Context) async throws -> ValueType {
try await computeValue(BuildContext(functionInterface: fi, context: ctx))
}
}
extension LLBCASFileTree {
-
public static func load<X: FXKey>(id: LLBDataID, in ctx: BuildContext<X>) async throws -> LLBCASFileTree {
-
try await load(id: id, from: ctx.db, ctx.context).get()
+
static func load<X: FXKey>(id: LLBDataID, in ctx: BuildContext<X>) async throws -> LLBCASFileTree {
+
try await load(id: id, from: ctx.db, ctx.context).get()
}
-
public func remove<X: FXKey>(path: AbsolutePath, in ctx: BuildContext<X>) async throws -> LLBCASFileTree {
-
try await remove(path: path, in: ctx.db, ctx.context).get()
-
}
+
func remove<X: FXKey>(path: AbsolutePath, in ctx: BuildContext<X>) async throws -> LLBCASFileTree {
+
try await remove(path: path, in: ctx.db, ctx.context).get()
+
}
-
public func traverse<X: FXKey>(root: AbsolutePath, in ctx: BuildContext<X>, _ callback: (AbsolutePath, LLBDataID, LLBDirectoryEntry) async throws -> Void) async throws {
-
try await traverse(root: root, in: ctx.db, ctx.context, callback)
-
}
+
func traverse<X: FXKey>(root: AbsolutePath, in ctx: BuildContext<X>, _ callback: (AbsolutePath, LLBDataID, LLBDirectoryEntry) async throws -> Void) async throws {
+
try await traverse(root: root, in: ctx.db, ctx.context, callback)
+
}
}
-16
Sources/PterodactylBuild/FXValue+Conformances.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import llbuild2fx
-
import PterodactylSyntax
-
-
extension SyntaxTree: FXValue {}
-
extension Token: FXValue {}
-
extension Graph: FXValue where Vertex: Codable {}
-
extension LineMap: FXValue {}
-
-
extension String: @retroactive FXValue {}
-
extension Set: @retroactive FXValue where Element: Codable {}
-
extension LLBDataID: @retroactive FXValue {}
+34
Sources/PterodactylBuild/Keys/AnalyseImports.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import Foundation
+
import TSCBasic
+
import llbuild2fx
+
+
extension Keys {
+
struct AnalyseImports: BuildKey {
+
typealias ValueType = [UnitName]
+
let blobId: LLBDataID
+
+
func computeValue(_ ctx: BuildContext<Self>) async throws -> [UnitName] {
+
let contents = try await ctx.load(blobId)
+
let code = try await String(decoding: Data(ctx.read(blob: contents.blob!)), as: UTF8.self)
+
+
var results: [UnitName] = []
+
let lines = code.split(separator: "\n", omittingEmptySubsequences: false)
+
+
for line in lines {
+
let trimmed = line.trimmingCharacters(in: .whitespaces)
+
guard trimmed.hasPrefix("import ") else { continue }
+
let parts = trimmed.split(separator: " ", maxSplits: 1, omittingEmptySubsequences: true)
+
if parts.count == 2 {
+
let name = parts[1].trimmingCharacters(in: .whitespaces)
+
results.append(UnitName(name: name))
+
}
+
}
+
+
return results
+
}
+
}
+
}
-25
Sources/PterodactylBuild/Keys/Blob/GetLineMap.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import PterodactylSyntax
-
import llbuild2fx
-
-
extension Keys.Blob {
-
public struct GetLineMap: BuildKey {
-
public let blobId: LLBDataID
-
public init(blobId: LLBDataID) {
-
self.blobId = blobId
-
}
-
-
public typealias ValueType = PterodactylSyntax.LineMap
-
-
public static let versionDependencies: [any FXVersioning.Type] = [ReadContents.self]
-
-
public func computeValue(_ ctx: BuildContext<Self>) async throws -> ValueType {
-
let code = try await ctx.request(ReadContents(blobId: blobId))
-
return PterodactylSyntax.LineMap(source: code)
-
}
-
}
-
}
-32
Sources/PterodactylBuild/Keys/Blob/ParseDocument.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import PterodactylSyntax
-
import llbuild2fx
-
import Logging
-
-
extension Keys.Blob {
-
public struct ParseDocument: BuildKey {
-
public let blobId: LLBDataID
-
public init(blobId: LLBDataID) {
-
self.blobId = blobId
-
}
-
-
public struct ValueType: Codable, FXValue {
-
public let tree: PterodactylSyntax.SyntaxTree
-
public let diagnostics: [Diagnostic]
-
}
-
-
public static let versionDependencies: [any FXVersioning.Type] = [ReadContents.self, Tokenise.self]
-
-
public func computeValue(_ ctx: BuildContext<Self>) async throws -> ValueType {
-
let code = try await ctx.request(ReadContents(blobId: blobId))
-
let tokens = try await ctx.request(Tokenise(blobId: blobId))
-
var parser = Parser(source: code, tokens: tokens)
-
PterodactylSyntax.Document.parse(&parser, recovery: [])
-
return ValueType(tree: parser.builder.tree, diagnostics: parser.diagnostics)
-
}
-
}
-
}
-31
Sources/PterodactylBuild/Keys/Blob/ParseImports.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import llbuild2fx
-
import PterodactylSyntax
-
-
extension Keys.Blob {
-
struct ParseImports: BuildKey {
-
let blobId: LLBDataID
-
-
typealias ValueType = [UnitName]
-
-
static let versionDependencies: [any FXVersioning.Type] = [ReadContents.self]
-
-
func computeValue(_ ctx: BuildContext<Self>) async throws -> [UnitName] {
-
let code = try await ctx.request(ReadContents(blobId: blobId))
-
var importParser = ImportParser(input: code)
-
importParser.parseHeader()
-
-
return importParser.imports.map { name in
-
UnitName(basename: name)
-
}
-
}
-
}
-
}
-
-
-
-
-7
Sources/PterodactylBuild/Keys/Blob/README.md
···
-
<!--
-
SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
-
SPDX-License-Identifier: MPL-2.0
-
-->
-
-
Certain operations do not require knowledge of the entire source tree, only a specific blob inside the source tree. These include import analysis, tokenisation, line maps, parsing, etc.
-25
Sources/PterodactylBuild/Keys/Blob/ReadContents.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import llbuild2fx
-
-
extension Keys.Blob {
-
public struct ReadContents: BuildKey {
-
let blobId: LLBDataID
-
-
public init(blobId: LLBDataID) {
-
self.blobId = blobId
-
}
-
-
public typealias ValueType = String
-
-
public static let versionDependencies: [any FXVersioning.Type] = []
-
-
public func computeValue(_ ctx: BuildContext<Self>) async throws -> String {
-
let contents = try await ctx.load(blobId)
-
return try await String(decoding: Data(ctx.read(blob: contents.blob!)), as: UTF8.self)
-
}
-
}
-
}
-24
Sources/PterodactylBuild/Keys/Blob/Tokenise.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import llbuild2fx
-
import PterodactylSyntax
-
-
extension Keys.Blob {
-
struct Tokenise: BuildKey {
-
let blobId: LLBDataID
-
-
typealias ValueType = [Token]
-
-
static let versionDependencies: [any FXVersioning.Type] = [ReadContents.self]
-
-
func computeValue(_ ctx: BuildContext<Self>) async throws -> ValueType {
-
let code = try await ctx.request(ReadContents(blobId: blobId))
-
var lexer = PterodactylSyntax.Lexer(input: code)
-
let flatTokens = lexer.tokenize()
-
return BlockLayoutProcessor(tokens: flatTokens).layout()
-
}
-
}
-
}
+33
Sources/PterodactylBuild/Keys/DependencyGraphOfSourceTree.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import Foundation
+
import TSCBasic
+
import llbuild2fx
+
+
extension Graph: FXValue where Vertex: Codable {}
+
+
extension Keys {
+
struct DependencyGraphOfSourceTree: BuildKey {
+
typealias ValueType = Graph<UnitName>
+
+
let sourceTreeId: LLBDataID
+
static let versionDependencies: [any FXVersioning.Type] = [Keys.UnitMapOfSourceTree.self, Keys.AnalyseImports.self]
+
+
func computeValue(_ ctx: BuildContext<Self>) async throws -> Graph<UnitName> {
+
let unitMap = try await ctx.request(Keys.UnitMapOfSourceTree(sourceTreeId: sourceTreeId))
+
var edges: [UnitName: Set<UnitName>] = [:]
+
+
for (unitName, unitInfo) in unitMap.units {
+
if edges[unitName] == nil { edges[unitName] = [] }
+
let imports = try await ctx.request(Keys.AnalyseImports(blobId: unitInfo.blobId))
+
for importedUnitName in imports {
+
edges[unitName]!.insert(importedUnitName)
+
}
+
}
+
+
return Graph(edges: edges)
+
}
+
}
+
}
+7
Sources/PterodactylBuild/Keys/Keys.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import Foundation
+
+
enum Keys {}
+35
Sources/PterodactylBuild/Keys/NarrowSourceTree.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import Foundation
+
import TSCBasic
+
import llbuild2fx
+
+
extension Keys {
+
/// Narrows a source tree to just the transitive dependencies of a given unit
+
struct NarrowSourceTree: BuildKey {
+
struct ValueType: Codable, FXValue {
+
let sourceTreeId: LLBDataID
+
}
+
+
let sourceTreeId: LLBDataID
+
let unitName: UnitName
+
+
static let versionDependencies: [any FXVersioning.Type] = [TransitiveDependencies.self, UnitMapOfSourceTree.self]
+
+
func computeValue(_ ctx: BuildContext<Self>) async throws -> ValueType {
+
let dependencies = try await ctx.request(TransitiveDependencies(sourceTreeId: sourceTreeId, unitName: unitName)).dependencies
+
let unitMap = try await ctx.request(UnitMapOfSourceTree(sourceTreeId: sourceTreeId))
+
+
var sourceTree = try await LLBCASFileTree.load(id: sourceTreeId, in: ctx)
+
for (unitName, unitInfo) in unitMap.units {
+
if !dependencies.contains(unitName) {
+
sourceTree = try await sourceTree.remove(path: unitInfo.path, in: ctx)
+
}
+
}
+
+
return ValueType(sourceTreeId: sourceTree.id)
+
}
+
}
+
}
+32
Sources/PterodactylBuild/Keys/SourceCode.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import Foundation
+
import TSCBasic
+
import llbuild2fx
+
+
extension Keys {
+
struct SourceCode: BuildKey {
+
struct ValueType: Codable, FXValue {
+
let code: String
+
}
+
+
enum SourceCodeError: Error {
+
case unitNotFound
+
}
+
+
let sourceTreeId: LLBDataID
+
let unitName: UnitName
+
+
static let versionDependencies: [any FXVersioning.Type] = [UnitMapOfSourceTree.self]
+
+
func computeValue(_ ctx: BuildContext<Self>) async throws -> ValueType {
+
let unitMap = try await ctx.request(UnitMapOfSourceTree(sourceTreeId: sourceTreeId))
+
guard let unitInfo = unitMap.units[unitName] else { throw SourceCodeError.unitNotFound }
+
let contents = try await ctx.load(unitInfo.blobId)
+
let code = try await String(decoding: Data(ctx.read(blob: contents.blob!)), as: UTF8.self)
+
return ValueType(code: code)
+
}
+
}
+
}
-22
Sources/PterodactylBuild/Keys/SourceTree/GetDependencies.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import llbuild2fx
-
-
extension Keys.SourceTree {
-
struct GetDependencies: BuildKey {
-
let sourceTreeId: LLBDataID
-
let unitName: UnitName
-
-
typealias ValueType = Set<UnitName>
-
-
static let versionDependencies: [any FXVersioning.Type] = [Keys.SourceTree.GetDependencyGraph.self]
-
-
func computeValue(_ ctx: BuildContext<Self>) async throws -> ValueType {
-
let graph = try await ctx.request(Keys.SourceTree.GetDependencyGraph(sourceTreeId: sourceTreeId))
-
return graph.verticesReachableFrom(unitName)
-
}
-
}
-
}
-31
Sources/PterodactylBuild/Keys/SourceTree/GetDependencyGraph.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import llbuild2fx
-
-
extension Keys.SourceTree {
-
struct GetDependencyGraph: BuildKey {
-
let sourceTreeId: LLBDataID
-
-
typealias ValueType = Graph<UnitName>
-
-
static let versionDependencies: [any FXVersioning.Type] = [Keys.SourceTree.GetUnitMap.self, Keys.Blob.ParseImports.self]
-
-
func computeValue(_ ctx: BuildContext<Self>) async throws -> Graph<UnitName> {
-
let unitMap = try await ctx.request(Keys.SourceTree.GetUnitMap(sourceTreeId: sourceTreeId))
-
var edges: [UnitName: Set<UnitName>] = [:]
-
-
for (unitName, unitInfo) in unitMap.units {
-
if edges[unitName] == nil { edges[unitName] = [] }
-
let imports = try await ctx.request(Keys.Blob.ParseImports(blobId: unitInfo.blobId))
-
for importedUnitName in imports {
-
edges[unitName]!.insert(importedUnitName)
-
}
-
}
-
-
return Graph(edges: edges)
-
}
-
}
-
}
-25
Sources/PterodactylBuild/Keys/SourceTree/GetUnitMap.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import llbuild2fx
-
-
extension Keys.SourceTree {
-
struct GetUnitMap: BuildKey {
-
let sourceTreeId: LLBDataID
-
-
typealias ValueType = UnitMap
-
-
func computeValue(_ ctx: BuildContext<Self>) async throws -> UnitMap {
-
let sourceTree = try await LLBCASFileTree.load(id: sourceTreeId, in: ctx)
-
var units: [UnitName: UnitInfo] = [:]
-
try await sourceTree.traverse(root: .root, in: ctx) { path, blobID, directoryEntry in
-
let unitName = UnitName.fromPath(path)
-
units[unitName] = UnitInfo(path: path, blobId: blobID)
-
}
-
-
return UnitMap(units: units)
-
}
-
}
-
}
-32
Sources/PterodactylBuild/Keys/SourceTree/NarrowToUnit.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import llbuild2fx
-
-
extension Keys.SourceTree {
-
/// Narrows a source tree to just the transitive dependencies of a given unit
-
struct NarrowToUnit: BuildKey {
-
let sourceTreeId: LLBDataID
-
let unitName: UnitName
-
-
typealias ValueType = LLBDataID
-
-
static let versionDependencies: [any FXVersioning.Type] = [GetDependencies.self, GetUnitMap.self]
-
-
func computeValue(_ ctx: BuildContext<Self>) async throws -> ValueType {
-
let dependencies = try await ctx.request(GetDependencies(sourceTreeId: sourceTreeId, unitName: unitName))
-
let unitMap = try await ctx.request(GetUnitMap(sourceTreeId: sourceTreeId))
-
-
var sourceTree = try await LLBCASFileTree.load(id: sourceTreeId, in: ctx)
-
for (unitName, unitInfo) in unitMap.units {
-
if !dependencies.contains(unitName) {
-
sourceTree = try await sourceTree.remove(path: unitInfo.path, in: ctx)
-
}
-
}
-
-
return sourceTree.id
-
}
-
}
-
}
+25
Sources/PterodactylBuild/Keys/TransitiveDependencies.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import Foundation
+
import TSCBasic
+
import llbuild2fx
+
+
extension Keys {
+
struct TransitiveDependencies: BuildKey {
+
struct ValueType: Codable, FXValue {
+
var dependencies: Set<UnitName>
+
}
+
+
let sourceTreeId: LLBDataID
+
let unitName: UnitName
+
+
static let versionDependencies: [any FXVersioning.Type] = [Keys.DependencyGraphOfSourceTree.self]
+
+
func computeValue(_ ctx: BuildContext<Self>) async throws -> ValueType {
+
let graph = try await ctx.request(Keys.DependencyGraphOfSourceTree(sourceTreeId: sourceTreeId))
+
return ValueType(dependencies: graph.verticesReachableFrom(unitName))
+
}
+
}
+
}
+25
Sources/PterodactylBuild/Keys/UnitMapOfSourceTree.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import Foundation
+
import TSCBasic
+
import llbuild2fx
+
+
extension Keys {
+
struct UnitMapOfSourceTree: BuildKey {
+
typealias ValueType = UnitMap
+
let sourceTreeId: LLBDataID
+
+
func computeValue(_ ctx: BuildContext<Self>) async throws -> UnitMap {
+
let sourceTree = try await LLBCASFileTree.load(id: sourceTreeId, in: ctx)
+
var units: [UnitName: UnitInfo] = [:]
+
try await sourceTree.traverse(root: .root, in: ctx) { path, blobID, directoryEntry in
+
let unitName = UnitName.fromPath(path)
+
units[unitName] = UnitInfo(path: path, blobId: blobID)
+
}
+
+
return UnitMap(units: units)
+
}
+
}
+
}
-10
Sources/PterodactylBuild/Keys.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
public enum Keys {
-
public enum Blob {}
-
public enum SourceTree {}
-
}
+5 -5
Sources/PterodactylBuild/Types/UnitName.swift
···
// SPDX-License-Identifier: MPL-2.0
import Foundation
-
import TSCBasic
import llbuild2fx
+
import TSCBasic
struct UnitName: Codable, Equatable, Hashable {
-
var basename: String
+
var name: String
}
extension UnitName: FXValue {}
extension UnitName {
-
static func fromPath(_ path: AbsolutePath) -> Self {
-
Self(basename: path.basenameWithoutExt)
-
}
+
static func fromPath(_ path: AbsolutePath) -> Self {
+
Self(name: path.basenameWithoutExt)
+
}
}
-19
Sources/PterodactylLanguageServer/AbsolutePath+URI.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import TSCBasic
-
import LanguageServerProtocol
-
-
extension AbsolutePath {
-
public static func fromDocumentUri(_ uri: DocumentUri) throws -> Self {
-
guard let url = URL(string: uri) else {
-
throw URLError(.badURL)
-
}
-
guard url.isFileURL else {
-
throw URLError(.unsupportedURL)
-
}
-
return try Self(validating: url.path)
-
}
-
}
-17
Sources/PterodactylLanguageServer/Archive/LLBDeclFileTree+Singleton.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import TSCBasic
-
import llbuild2fx
-
-
extension LLBDeclFileTree {
-
public static func file(absolutePath: AbsolutePath, contents: String) -> Self {
-
let components = absolutePath.components
-
let leaf = LLBDeclFileTree.file(contents)
-
return components.dropFirst().reversed().reduce(leaf) { tail, name in
-
.directory(files: [name: tail])
-
}
-
}
-
}
-40
Sources/PterodactylLanguageServer/Archive/SourceTreeManager.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
//import LanguageServerProtocol
-
//import PterodactylBuild
-
//import TSCBasic
-
//import llbuild2fx
-
//
-
//actor SourceTreeManager {
-
// private let buildEngine: FXEngine
-
// private let casClient: LLBCASFSClient
-
// private let casContext: TSCUtility.Context
-
// private(set) var sourceTree: LLBCASFileTree
-
//
-
// init(buildEngine: FXEngine, casClient: LLBCASFSClient, casContext: TSCUtility.Context, sourceTree: LLBCASFileTree? = nil) async throws {
-
// self.buildEngine = buildEngine
-
// self.casClient = casClient
-
// self.casContext = casContext
-
// if let sourceTree {
-
// self.sourceTree = sourceTree
-
// } else {
-
// self.sourceTree = try await casClient.storeDir(.dir([:]), casContext).get()
-
// }
-
// }
-
//
-
// func setBufferText(uri: DocumentUri, text: String) async throws -> LLBCASFileTree {
-
// let path = try AbsolutePath.fromDocumentUri(uri)
-
// let singletonDeclTree = LLBDeclFileTree.file(absolutePath: path, contents: text)
-
// let singletonTree: LLBCASFileTree = try await casClient.storeDir(singletonDeclTree, casContext).get()
-
// self.sourceTree = try await sourceTree.merge(with: singletonTree, in: casClient.db, casContext).get()
-
// return sourceTree
-
// }
-
//
-
// func getBufferText(uri: DocumentUri) async throws -> String? {
-
// let path = try AbsolutePath.fromDocumentUri(uri)
-
// guard let (id, _) = try await sourceTree.lookup(path: path, in: casClient.db, casContext).get() else { return nil }
-
// return try await buildEngine.build(key: Keys.Blob.ReadContents(blobId: id), casContext).get()
-
// }
-
//}
-269
Sources/PterodactylLanguageServer/EventHandler.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import JSONRPC
-
import LanguageServer
-
import LanguageServerProtocol
-
import Logging
-
import PterodactylBuild
-
import PterodactylSyntax
-
import TSCBasic
-
import llbuild2fx
-
-
final class EventHandler {
-
private let connection: JSONRPCClientConnection
-
private let buildEngine: FXEngine
-
private let casContext: TSCUtility.Context
-
private let casClient: LLBCASFSClient
-
-
var storedBlobs: [DocumentUri: (blobId: LLBDataID, version: Int?)] = [:]
-
-
init(connection: JSONRPCClientConnection) {
-
self.connection = connection
-
-
let group = LLBMakeDefaultDispatchGroup()
-
let db = LLBInMemoryCASDatabase(group: group)
-
let functionCache = FXInMemoryFunctionCache(group: group)
-
let executor = FXLocalExecutor()
-
self.buildEngine = FXEngine(group: group, db: db, functionCache: functionCache, executor: executor)
-
self.casClient = LLBCASFSClient(db)
-
self.casContext = Context()
-
}
-
-
func storeBlob(text: String, uri: DocumentUri, version: Int?) async throws -> LLBDataID {
-
if let stored = storedBlobs[uri], let version, let storedVersion = stored.version, storedVersion >= version {
-
return stored.blobId
-
}
-
-
let blobId: LLBDataID = try await casClient.store(LLBByteBuffer(string: text), casContext).get()
-
storedBlobs[uri] = (blobId: blobId, version: version)
-
return blobId
-
}
-
-
func publishLiveDiagnostics(blobId: LLBDataID, uri: DocumentUri, version: Int?) async throws {
-
let lineMap = try await buildEngine.build(key: Keys.Blob.GetLineMap(blobId: blobId), casContext).get()
-
let parseResult = try await buildEngine.build(key: Keys.Blob.ParseDocument(blobId: blobId), casContext).get()
-
let diagnostics = parseResult.diagnostics.map { $0.lspDiagnostic(lineMap: lineMap) }
-
let publishParams = PublishDiagnosticsParams(uri: uri, version: version, diagnostics: diagnostics)
-
try await connection.sendNotification(.textDocumentPublishDiagnostics(publishParams))
-
}
-
}
-
-
-
extension EventHandler: LanguageServer.EventHandler {
-
var textDocumentSinkOptions: TextDocumentSyncOptions {
-
TextDocumentSyncOptions(
-
openClose: true,
-
change: .full,
-
-
save: .optionA(false)
-
)
-
}
-
-
var completionOptions: CompletionOptions {
-
CompletionOptions(
-
workDoneProgress: false,
-
triggerCharacters: [], // TODO
-
allCommitCharacters: nil,
-
resolveProvider: false,
-
completionItem: nil
-
)
-
}
-
-
var semanticTokensLegend: SemanticTokensLegend {
-
SemanticTokensLegend(tokenTypes: SemanticTokenTypes.allStrings, tokenModifiers: SemanticTokenModifiers.allStrings)
-
}
-
-
var semanticTokensOptions: SemanticTokensOptions {
-
SemanticTokensOptions(legend: semanticTokensLegend, full: .optionB(SemanticTokensClientCapabilities.Requests.Full(delta: false)))
-
}
-
-
func initialize(id: JSONId, params: InitializeParams) async -> Response<InitializationResponse> {
-
Logger.shared.debug("Received initialize request")
-
var serverCapabilities = ServerCapabilities()
-
serverCapabilities.textDocumentSync = .optionA(textDocumentSinkOptions)
-
serverCapabilities.completionProvider = completionOptions
-
serverCapabilities.hoverProvider = .optionA(false)
-
serverCapabilities.semanticTokensProvider = .optionA(semanticTokensOptions)
-
serverCapabilities.foldingRangeProvider = .optionA(true)
-
let response = InitializationResponse(
-
capabilities: serverCapabilities,
-
serverInfo: nil
-
)
-
return .success(response)
-
}
-
-
func textDocumentDidChange(_ params: DidChangeTextDocumentParams) async {
-
guard let text = params.contentChanges.first?.text else { return }
-
do {
-
let blobId: LLBDataID = try await storeBlob(text: text, uri: params.textDocument.uri, version: params.textDocument.version)
-
try await publishLiveDiagnostics(blobId: blobId, uri: params.textDocument.uri, version: params.textDocument.version)
-
} catch {}
-
}
-
-
func textDocumentDidOpen(_ params: DidOpenTextDocumentParams) async {
-
let text = params.textDocument.text
-
do {
-
let blobId: LLBDataID = try await storeBlob(text: text, uri: params.textDocument.uri, version: params.textDocument.version)
-
try await publishLiveDiagnostics(blobId: blobId, uri: params.textDocument.uri, version: params.textDocument.version)
-
} catch {}
-
}
-
-
func semanticTokensFull(id: JSONId, params: SemanticTokensParams) async -> Response<SemanticTokensResponse> {
-
guard let storedBlob = storedBlobs[params.textDocument.uri] else { return .success(nil) }
-
-
do {
-
let lineMap = try await buildEngine.build(key: Keys.Blob.GetLineMap(blobId: storedBlob.blobId), casContext).get()
-
let parseResult = try await buildEngine.build(key: Keys.Blob.ParseDocument(blobId: storedBlob.blobId), casContext).get()
-
let cursor = SyntaxCursor(lineMap: lineMap, node: .tree(parseResult.tree), utf16Offset: 0)
-
return .success(SemanticTokensResponse(SemanticTokens(resultId: nil, tokens: cursor.semanticTokens)))
-
} catch {
-
return .success(nil)
-
}
-
}
-
-
func foldingRange(id: JSONId, params: FoldingRangeParams) async -> Response<FoldingRangeResponse> {
-
guard let storedBlob = storedBlobs[params.textDocument.uri] else { return .success(nil) }
-
-
do {
-
let lineMap = try await buildEngine.build(key: Keys.Blob.GetLineMap(blobId: storedBlob.blobId), casContext).get()
-
let parseResult = try await buildEngine.build(key: Keys.Blob.ParseDocument(blobId: storedBlob.blobId), casContext).get()
-
let cursor = SyntaxCursor(lineMap: lineMap, node: .tree(parseResult.tree), utf16Offset: 0)
-
return .success(FoldingRangeResponse(cursor.foldingRanges))
-
} catch {
-
return .success(nil)
-
}
-
}
-
-
-
func typeHierarchySupertypes(
-
id: JSONRPC.JSONId,
-
params: TypeHierarchySupertypesParams
-
) async -> Response<TypeHierarchySupertypesResponse> {
-
.success(nil)
-
}
-
-
func typeHierarchySubtypes(
-
id: JSONId,
-
params: TypeHierarchySubtypesParams
-
) async -> Response<TypeHierarchySubtypesResponse> {
-
.success(nil)
-
}
-
-
func internalError(_ error: any Error) async {
-
Logger.shared.error("Received error: \(error)")
-
}
-
-
func initialized(_ params: InitializedParams) async {
-
-
}
-
-
func exit() async {
-
Logger.shared.info("Received exit notification")
-
Foundation.exit(0)
-
}
-
-
func diagnostics(id: JSONId, params: DocumentDiagnosticParams) async -> Response<DocumentDiagnosticReport> {
-
return .success(.init(kind: .unchanged))
-
}
-
-
-
-
func textDocumentDidClose(_ params: DidCloseTextDocumentParams) async {
-
-
}
-
-
func textDocumentWillSave(_ params: WillSaveTextDocumentParams) async {
-
-
}
-
-
func textDocumentDidSave(_ params: DidSaveTextDocumentParams) async {
-
-
}
-
-
func protocolCancelRequest(_ params: CancelParams) async {
-
-
}
-
-
func protocolSetTrace(_ params: SetTraceParams) async {
-
-
}
-
-
func workspaceDidChangeWatchedFiles(_ params: DidChangeWatchedFilesParams) async {
-
-
}
-
-
func windowWorkDoneProgressCancel(_ params: WorkDoneProgressCancelParams) async {
-
-
}
-
-
func workspaceDidChangeWorkspaceFolders(_ params: DidChangeWorkspaceFoldersParams) async {
-
-
}
-
-
func workspaceDidChangeConfiguration(_ params: DidChangeConfigurationParams) async {
-
-
}
-
-
func workspaceDidCreateFiles(_ params: CreateFilesParams) async {
-
-
}
-
-
func workspaceDidRenameFiles(_ params: RenameFilesParams) async {
-
-
}
-
-
func workspaceDidDeleteFiles(_ params: DeleteFilesParams) async {
-
-
}
-
-
func shutdown(id: JSONId) async {}
-
func workspaceInlayHintRefresh(id: JSONId) async {}
-
func workspaceExecuteCommand(id: JSONId, params: ExecuteCommandParams) async -> Response<LSPAny?> { .success(nil) }
-
func workspaceWillCreateFiles(id: JSONId, params: CreateFilesParams) async -> Response<WorkspaceEdit?> { .success(nil) }
-
func workspaceWillRenameFiles(id: JSONId, params: RenameFilesParams) async -> Response<WorkspaceEdit?> { .success(nil) }
-
func workspaceWillDeleteFiles(id: JSONId, params: DeleteFilesParams) async -> Response<WorkspaceEdit?> { .success(nil) }
-
func workspaceSymbol(id: JSONId, params: WorkspaceSymbolParams) async -> Response<WorkspaceSymbolResponse> { .success(nil) }
-
// func workspaceSymbolResolve(id: JSONId, params: WorkspaceSymbol) async -> Response<WorkspaceSymbol> { .success(nil) }
-
func textDocumentWillSaveWaitUntil(id: JSONId, params: WillSaveTextDocumentParams) async -> Response<[TextEdit]?> { .success(nil) }
-
func completion(id: JSONId, params: CompletionParams) async -> Response<CompletionResponse> {
-
.success(CompletionResponse(.optionB(.init(isIncomplete: false, items: []))))
-
}
-
// func completionItemResolve(id: JSONId, params: CompletionItem) async -> Response<CompletionItem> { .success(nil) }
-
func hover(id: JSONId, params: TextDocumentPositionParams) async -> Response<HoverResponse> { .success(nil) }
-
func signatureHelp(id: JSONId, params: TextDocumentPositionParams) async -> Response<SignatureHelpResponse> { .success(nil) }
-
func declaration(id: JSONId, params: TextDocumentPositionParams) async -> Response<DeclarationResponse> { .success(nil) }
-
func definition(id: JSONId, params: TextDocumentPositionParams) async -> Response<DefinitionResponse> { .success(nil) }
-
func typeDefinition(id: JSONId, params: TextDocumentPositionParams) async -> Response<TypeDefinitionResponse> { .success(nil) }
-
func implementation(id: JSONId, params: TextDocumentPositionParams) async -> Response<ImplementationResponse> { .success(nil) }
-
func documentHighlight(id: JSONId, params: DocumentHighlightParams) async -> Response<DocumentHighlightResponse> { .success(nil) }
-
func documentSymbol(id: JSONId, params: DocumentSymbolParams) async -> Response<DocumentSymbolResponse> { .success(nil) }
-
func codeAction(id: JSONId, params: CodeActionParams) async -> Response<CodeActionResponse> { .success(nil) }
-
// func codeActionResolve(id: JSONId, params: CodeAction) async -> Response<CodeAction> { .success(nil) }
-
func codeLens(id: JSONId, params: CodeLensParams) async -> Response<CodeLensResponse> { .success(nil) }
-
// func codeLensResolve(id: JSONId, params: CodeLens) async -> Response<CodeLens> { .success(nil) }
-
func selectionRange(id: JSONId, params: SelectionRangeParams) async -> Response<SelectionRangeResponse> { .success(nil) }
-
func linkedEditingRange(id: JSONId, params: LinkedEditingRangeParams) async -> Response<LinkedEditingRangeResponse> { .success(nil) }
-
func prepareCallHierarchy(id: JSONId, params: CallHierarchyPrepareParams) async -> Response<CallHierarchyPrepareResponse> { .success(nil) }
-
func prepareRename(id: JSONId, params: PrepareRenameParams) async -> Response<PrepareRenameResponse> { .success(nil) }
-
func prepareTypeHeirarchy(id: JSONId, params: TypeHierarchyPrepareParams) async -> Response<PrepareTypeHeirarchyResponse> { .success(nil) }
-
func rename(id: JSONId, params: RenameParams) async -> Response<RenameResponse> { .success(nil) }
-
func inlayHint(id: JSONId, params: InlayHintParams) async -> Response<InlayHintResponse> { .success(nil) }
-
func inlayHintResolve(id: JSONId, params: InlayHint) async -> Response<InlayHintResponse> { .success(nil) }
-
func documentLink(id: JSONId, params: DocumentLinkParams) async -> Response<DocumentLinkResponse> { .success(nil) }
-
// func documentLinkResolve(id: JSONId, params: DocumentLink) async -> Response<DocumentLink> { .success(nil) }
-
// func documentColor(id: JSONId, params: DocumentColorParams) async -> Response<DocumentColorResponse> { .success(nil) }
-
// func colorPresentation(id: JSONId, params: ColorPresentationParams) async -> Response<ColorPresentationResponse> { .success(nil) }
-
func formatting(id: JSONId, params: DocumentFormattingParams) async -> Response<FormattingResult> { .success(nil) }
-
func rangeFormatting(id: JSONId, params: DocumentRangeFormattingParams) async -> Response<FormattingResult> { .success(nil) }
-
func onTypeFormatting(id: JSONId, params: DocumentOnTypeFormattingParams) async -> Response<FormattingResult> { .success(nil) }
-
func references(id: JSONId, params: ReferenceParams) async -> Response<ReferenceResponse> { .success(nil) }
-
func moniker(id: JSONId, params: MonikerParams) async -> Response<MonikerResponse> { .success(nil) }
-
func semanticTokensFullDelta(id: JSONId, params: SemanticTokensDeltaParams) async -> Response<SemanticTokensDeltaResponse> { .success(nil) }
-
func semanticTokensRange(id: JSONId, params: SemanticTokensRangeParams) async -> Response<SemanticTokensResponse> { .success(nil) }
-
func callHierarchyIncomingCalls(id: JSONId, params: CallHierarchyIncomingCallsParams) async -> Response<CallHierarchyIncomingCallsResponse> { .success(nil) }
-
func callHierarchyOutgoingCalls(id: JSONId, params: CallHierarchyOutgoingCallsParams) async -> Response<CallHierarchyOutgoingCallsResponse> { .success(nil) }
-
func custom(id: JSONId, method: String, params: LSPAny) async -> Response<LSPAny> { .success(nil) }
-
}
-63
Sources/PterodactylLanguageServer/Logger.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import Logging
-
-
struct FileLogHandler: LogHandler {
-
private let label: String
-
private let fileHandle: FileHandle
-
var logLevel: Logger.Level = .info
-
var metadata: Logger.Metadata = [:]
-
-
init(label: String, fileURL: URL) {
-
self.label = label
-
-
// Ensure file exists
-
if !FileManager.default.fileExists(atPath: fileURL.path) {
-
FileManager.default.createFile(atPath: fileURL.path, contents: nil)
-
}
-
-
// Open file for updating (read/write)
-
self.fileHandle = try! FileHandle(forUpdating: fileURL)
-
self.fileHandle.seekToEndOfFile()
-
}
-
-
subscript(metadataKey key: String) -> Logger.Metadata.Value? {
-
get { metadata[key] }
-
set { metadata[key] = newValue }
-
}
-
-
func log(
-
level: Logger.Level,
-
message: Logger.Message,
-
metadata: Logger.Metadata?,
-
source: String,
-
file: String,
-
function: String,
-
line: UInt
-
) {
-
var fullMetadata = self.metadata
-
metadata?.forEach { fullMetadata[$0] = $1 }
-
-
let line = "[\(level)] \(message)\n"
-
if let data = line.data(using: .utf8) {
-
fileHandle.write(data)
-
}
-
}
-
}
-
-
extension Logger {
-
static let shared: Self = {
-
let logFile = URL(fileURLWithPath: "/tmp/pterodactyl-language-server.log")
-
-
LoggingSystem.bootstrap { label in
-
FileLogHandler(label: label, fileURL: logFile)
-
}
-
-
var logger = Logger(label: "org.pterodactyl.language-server")
-
logger.logLevel = .debug
-
return logger
-
}()
-
}
-54
Sources/PterodactylLanguageServer/PterodactylLanguageServer.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import JSONRPC
-
import LanguageServer
-
import LanguageServerProtocol
-
import Logging
-
-
final class Server {
-
private let connection: JSONRPCClientConnection
-
private let eventHandler: EventHandler
-
-
init() {
-
let channel = DataChannel.stdio()
-
connection = JSONRPCClientConnection(channel)
-
eventHandler = EventHandler(connection: connection)
-
}
-
-
func start() async throws {
-
do {
-
Logger.shared.debug("Starting")
-
try await startEventLoop()
-
} catch {
-
Logger.shared.error("Server error: \(error)")
-
throw error
-
}
-
}
-
-
func startEventLoop() async throws {
-
for await event in await connection.eventSequence {
-
try await handle(event: event)
-
}
-
}
-
-
func handle(event: ClientEvent) async throws {
-
switch event {
-
case .request(let id, let request):
-
await eventHandler.handleRequest(id: id, request: request)
-
case .notification(let notification):
-
await eventHandler.handleNotification(notification)
-
case .error(_): ()
-
}
-
}
-
}
-
-
@main
-
struct PterodactylLanguageServer {
-
static func main() async throws {
-
let server = Server()
-
try await server.start()
-
}
-
}
-100
Sources/PterodactylSyntax/BlockLayoutProcessor.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
fileprivate struct TokenLocation {
-
let startLine: Int
-
let startColumn: Int
-
}
-
-
fileprivate struct LocatedToken {
-
let token: Token
-
let location: TokenLocation
-
let lines: [String.SubSequence]
-
-
init(token: Token, location: TokenLocation) {
-
self.token = token
-
self.location = location
-
self.lines = token.text.split(separator: "\n", omittingEmptySubsequences: false)
-
}
-
-
var nextLocation: TokenLocation {
-
let startLine = location.startLine + lines.count - 1
-
let startColumn = if lines.count > 1 { lines.last!.utf16.count } else { lines.last?.utf16.count ?? location.startColumn }
-
return TokenLocation(startLine: startLine, startColumn: startColumn)
-
}
-
}
-
-
public struct BlockLayoutProcessor {
-
private let locatedTokens: [LocatedToken]
-
-
public init(tokens: [Token]) {
-
var locatedTokens: [LocatedToken] = []
-
var location = TokenLocation(startLine: 0, startColumn: 0)
-
for token in tokens {
-
let locatedToken = LocatedToken(token: token, location: location)
-
locatedTokens.append(locatedToken)
-
location = locatedToken.nextLocation
-
}
-
-
self.locatedTokens = locatedTokens
-
}
-
-
-
public func layout() -> [Token] {
-
var result: [Token] = []
-
var indentStack: [Int] = [0]
-
var previousLine = 0
-
var firstTokenInBlock = false
-
-
for (index, locatedToken) in locatedTokens.enumerated() {
-
guard locatedToken.token.kind != .eof else { break }
-
guard locatedToken.token.kind.canDetermineLayoutColumn else {
-
result.append(locatedToken.token)
-
continue
-
}
-
-
if locatedToken.location.startLine > previousLine {
-
while indentStack.count > 1 && locatedToken.location.startColumn < indentStack.last! {
-
indentStack.removeLast()
-
result.append(Token(kind: .blockEnd, text: ""))
-
}
-
-
if !firstTokenInBlock && indentStack.count > 1 && locatedToken.token.kind.isVisible && locatedToken.location.startColumn == indentStack.last! {
-
result.append(Token(kind: .blockSep, text: ""))
-
}
-
}
-
-
result.append(locatedToken.token)
-
-
if locatedToken.token.kind.isBlockHerald {
-
firstTokenInBlock = true
-
-
if let nextToken = locatedTokens[index...].first(where: { $0.location.startLine > locatedToken.location.startLine && $0.token.kind.canDetermineLayoutColumn }) {
-
result.append(Token(kind: .blockBegin, text: ""))
-
indentStack.append(nextToken.location.startColumn)
-
} else {
-
result.append(Token(kind: .blockBegin, text: ""))
-
result.append(Token(kind: .blockEnd, text: ""))
-
}
-
} else {
-
firstTokenInBlock = false
-
}
-
-
previousLine = locatedToken.location.startLine
-
}
-
-
while indentStack.count > 1 {
-
indentStack.removeLast()
-
result.append(Token(kind: .blockEnd, text: ""))
-
}
-
-
if let eof = locatedTokens.last, eof.token.kind == .eof {
-
result.append(eof.token)
-
}
-
-
return result
-
}
-
}
+25
Sources/PterodactylSyntax/Cursor.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import Foundation
+
+
public struct Cursor: Sendable {
+
let node: SyntaxTree.Child
+
let utf16Offset: Int
+
let children: [Cursor]
+
+
init(node: SyntaxTree.Child, utf16Offset: Int) {
+
self.node = node
+
self.utf16Offset = utf16Offset
+
+
var children: [Cursor] = []
+
var utf16Offset = utf16Offset
+
for childNode in node.children {
+
children.append(Self(node: childNode, utf16Offset: utf16Offset))
+
utf16Offset += childNode.utf16Length
+
}
+
+
self.children = children
+
}
+
}
-40
Sources/PterodactylSyntax/Diagnostic.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import LanguageServerProtocol
-
-
public struct Diagnostic: Equatable, Codable, Sendable {
-
public typealias Severity = LanguageServerProtocol.DiagnosticSeverity
-
-
public let message: String
-
public let severity: Severity
-
public let absoluteUtf16Range: Range<Int>
-
-
init(message: String, severity: Severity, absoluteRange: Range<Int>) {
-
self.message = message
-
self.severity = severity
-
self.absoluteUtf16Range = absoluteRange
-
}
-
-
init(message: String, absoluteRange: Range<Int>) {
-
self.init(message: message, severity: Severity.error, absoluteRange: absoluteRange)
-
}
-
-
func lspRange(lineMap: LineMap) -> LanguageServerProtocol.LSPRange {
-
let start = lineMap.location(at: absoluteUtf16Range.lowerBound)
-
let end = lineMap.location(at: absoluteUtf16Range.upperBound)
-
return LSPRange(
-
start: Position(line: start.line, character: start.column),
-
end: Position(line: end.line, character: end.column)
-
)
-
}
-
-
public func lspDiagnostic(lineMap: LineMap) -> LanguageServerProtocol.Diagnostic {
-
LanguageServerProtocol.Diagnostic(
-
range: lspRange(lineMap: lineMap),
-
severity: severity,
-
message: message
-
)
-
}
-
}
-62
Sources/PterodactylSyntax/FoldingRanges.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import LanguageServerProtocol
-
-
private enum ArraySymmetry {
-
case identity
-
case reverse
-
}
-
-
fileprivate extension Array {
-
func apply(symmetry: ArraySymmetry) -> any Collection<Element> {
-
switch symmetry {
-
case .identity: self
-
case .reverse: reversed()
-
}
-
}
-
}
-
-
extension SyntaxCursor {
-
private func firstVisibleNode(under symmetry: ArraySymmetry) -> SyntaxCursor? {
-
switch node {
-
case .token(let token, _):
-
return token.kind.isVisible ? self : nil
-
case .tree:
-
for child in children.apply(symmetry: symmetry) {
-
if let visibleChild = child.firstVisibleNode(under: symmetry) { return visibleChild }
-
}
-
-
return nil
-
}
-
}
-
-
private var visibleUtf16Range: Range<Int>? {
-
guard
-
let firstNode = firstVisibleNode(under: .identity),
-
let lastNode = firstVisibleNode(under: .reverse)
-
else { return nil }
-
return firstNode.utf16Range.lowerBound..<lastNode.utf16Range.upperBound
-
}
-
-
private func collectFoldingRanges(_ sink: inout [FoldingRange]) {
-
if let foldingRangeKind = node.tree?.metadata?.delimitedFoldingRangeKind, let visibleUtf16Range {
-
let startLocation = lineMap.location(at: visibleUtf16Range.lowerBound)
-
let endLocation = lineMap.location(at: visibleUtf16Range.upperBound)
-
let foldingRange = FoldingRange(startLine: startLocation.line, endLine: endLocation.line, kind: foldingRangeKind)
-
sink.append(foldingRange)
-
}
-
-
for child in children {
-
child.collectFoldingRanges(&sink)
-
}
-
}
-
-
public var foldingRanges: [FoldingRange] {
-
var sink: [FoldingRange] = []
-
collectFoldingRanges(&sink)
-
return sink
-
}
-
}
-46
Sources/PterodactylSyntax/Grammar/Document/Import.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
enum ImportName: Grammar {
-
static let kind = SyntaxTreeKind(name: "import.name")
-
static let kinds = [kind]
-
-
static func precondition(_ parser: inout Parser) -> Bool {
-
parser.isAt(kind: .identifier)
-
}
-
-
static func inside(_ parser: inout Parser, recovery: Set<TokenKind>) -> ParseResult {
-
parser.expect(kind: .identifier, metadata: TokenMetadata(semanticTokenType: .namespace), recovery: recovery)
-
return ParseResult(kind: Self.kind)
-
}
-
}
-
-
enum Import: Grammar {
-
static let kind = SyntaxTreeKind(name: "import")
-
static let kinds = [kind]
-
-
static func precondition(_ parser: inout Parser) -> Bool {
-
parser.isAt(kind: .keyword(.import))
-
}
-
-
static func inside(_ parser: inout Parser, recovery: Set<TokenKind>) -> ParseResult {
-
parser.expect(kind: .keyword(.import), metadata: TokenMetadata(semanticTokenType: .keyword), recovery: recovery)
-
parser.eatTrivia()
-
ImportName.parse(&parser, recovery: recovery)
-
return ParseResult(kind: Self.kind)
-
}
-
}
-
-
-
extension SyntaxView<ImportName> {
-
var text: String { cursor.node.text }
-
}
-
-
extension SyntaxView<Import> {
-
var name: SyntaxView<ImportName>? {
-
matchingSubview()
-
}
-
}
-21
Sources/PterodactylSyntax/Grammar/Document/Theory/Declaration/Lhs.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
extension Declaration {
-
enum Lhs: Grammar {
-
static let kind = SyntaxTreeKind(name: "declaration.lhs")
-
static let kinds = [kind]
-
-
static func precondition(_ parser: inout Parser) -> Bool {
-
parser.isAt(kind: .identifier)
-
}
-
-
static func inside(_ parser: inout Parser, recovery: Set<TokenKind>) -> ParseResult {
-
parser.expect(kind: .identifier, metadata: TokenMetadata(semanticTokenType: .method), recovery: recovery)
-
return ParseResult(kind: Self.kind)
-
}
-
}
-
}
-21
Sources/PterodactylSyntax/Grammar/Document/Theory/Declaration/Rhs.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
extension Declaration {
-
enum Rhs: Grammar {
-
static let kind = SyntaxTreeKind(name: "declaration.lhs")
-
static let kinds = [kind]
-
-
static func precondition(_ parser: inout Parser) -> Bool {
-
parser.isAt(kind: .identifier)
-
}
-
-
static func inside(_ parser: inout Parser, recovery: Set<TokenKind>) -> ParseResult {
-
parser.expect(kind: .identifier, metadata: TokenMetadata(semanticTokenType: .method), recovery: recovery)
-
return ParseResult(kind: Self.kind)
-
}
-
}
-
}
-53
Sources/PterodactylSyntax/Grammar/Document/Theory/Declaration.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
enum Declaration: Grammar {
-
enum Kinds {
-
static let claim = SyntaxTreeKind(name: "decl.claim")
-
static let refine = SyntaxTreeKind(name: "decl.refine")
-
static let define = SyntaxTreeKind(name: "decl.define")
-
}
-
-
static let kinds = [Kinds.claim, Kinds.refine, Kinds.define]
-
-
static func precondition(_ parser: inout Parser) -> Bool {
-
Lhs.precondition(&parser)
-
}
-
-
static let punctuationMap: [Punctuation: SyntaxTreeKind] = [
-
.colon: Kinds.claim,
-
.doubleLeftArrow: Kinds.refine,
-
.doubleRightArrow: Kinds.define
-
]
-
-
static func inside(_ parser: inout Parser, recovery: Set<TokenKind>) -> ParseResult {
-
let punctuations = punctuationMap.keys.map { TokenKind.punctuation($0) }
-
Lhs.parse(&parser, recovery: recovery.union(punctuations))
-
parser.eatTrivia()
-
-
var kind: SyntaxTreeKind = .error
-
for cell in punctuationMap {
-
if parser.eat(kind: .punctuation(cell.key), metadata: TokenMetadata(semanticTokenType: .operator)) {
-
kind = cell.value
-
break
-
}
-
}
-
-
if kind == .error {
-
parser.advance(error: "Expected one of \(punctuationMap.keys.map(\.rawValue)) in declaration")
-
}
-
-
parser.eatTrivia()
-
Rhs.parse(&parser, recovery: recovery)
-
-
return ParseResult(kind: kind)
-
}
-
}
-
-
extension SyntaxView<Declaration> {
-
var lhs: SyntaxView<Declaration.Lhs>? { matchingSubview() }
-
var rhs: SyntaxView<Declaration.Rhs>? { matchingSubview() }
-
}
-38
Sources/PterodactylSyntax/Grammar/Document/Theory/TheoryBlock.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
enum TheoryBlock: Grammar {
-
static let kind = SyntaxTreeKind(name: "theory.block")
-
static let kinds = [kind]
-
-
static func precondition(_ parser: inout Parser) -> Bool {
-
parser.isAt(kind: .keyword(.where))
-
}
-
-
static func inside(_ parser: inout Parser, recovery: Set<TokenKind>) -> ParseResult {
-
parser.expect(kind: .keyword(.where), metadata: TokenMetadata(semanticTokenType: .keyword), recovery: recovery.union([.keyword(.theory), .blockComment(terminated: true), .lineComment]))
-
-
if parser.eat(kind: .blockBegin, metadata: nil) {
-
parser.eatTrivia()
-
while parser.eat(kind: .blockSep, metadata: nil) {
-
Declaration.parse(&parser, recovery: recovery.union([.blockSep, .blockEnd]))
-
parser.eatTrivia()
-
}
-
_ = parser.eat(kind: .blockEnd, metadata: nil)
-
}
-
-
var metadata = SyntaxTreeMetadata()
-
metadata.delimitedFoldingRangeKind = .region
-
-
return ParseResult(kind: kind, metadata: metadata)
-
}
-
}
-
-
extension SyntaxView<TheoryBlock> {
-
var declarations: [SyntaxView<Declaration>] {
-
matchingSubviews()
-
}
-
}
-23
Sources/PterodactylSyntax/Grammar/Document/Theory/TheoryName.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
enum TheoryName: Grammar {
-
static let kind = SyntaxTreeKind(name: "theory.name")
-
static let kinds = [kind]
-
-
static func precondition(_ parser: inout Parser) -> Bool {
-
parser.isAt(kind: .identifier)
-
}
-
-
static func inside(_ parser: inout Parser, recovery: Set<TokenKind>) -> ParseResult {
-
parser.advance(metadata: TokenMetadata(semanticTokenType: .interface))
-
return ParseResult(kind: Self.kind)
-
}
-
}
-
-
extension SyntaxView<TheoryName> {
-
var text: String { cursor.node.text }
-
}
-33
Sources/PterodactylSyntax/Grammar/Document/Theory.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
enum Theory: Grammar {
-
static let kind = SyntaxTreeKind(name: "theory")
-
static let kinds = [kind]
-
-
static func precondition(_ parser: inout Parser) -> Bool {
-
parser.isAt(kind: .keyword(.theory))
-
}
-
-
static func inside(_ parser: inout Parser, recovery: Set<TokenKind>) -> ParseResult {
-
parser.expect(kind: .keyword(.theory), metadata: TokenMetadata(semanticTokenType: .keyword), recovery: recovery)
-
parser.eatTrivia()
-
if !TheoryName.tryParse(&parser, recovery: recovery) {
-
parser.advance(error: "Expected theory name")
-
}
-
-
parser.eatTrivia()
-
-
TheoryBlock.parse(&parser, recovery: recovery)
-
-
return ParseResult(kind: Self.kind)
-
}
-
}
-
-
extension SyntaxView<Theory> {
-
var name: SyntaxView<TheoryName>? { matchingSubview() }
-
var block: SyntaxView<TheoryBlock>? { matchingSubview() }
-
}
-51
Sources/PterodactylSyntax/Grammar/Document.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
public enum Document: Grammar {
-
public static let kind = SyntaxTreeKind(name: "document")
-
public static let kinds = [kind]
-
-
public static func precondition(_ parser: inout Parser) -> Bool {
-
true
-
}
-
-
public static func inside(_ parser: inout Parser, recovery: Set<TokenKind>) -> ParseResult {
-
parser.eatTrivia()
-
-
// Parse imports
-
while !parser.isAt(kind: .eof) {
-
parser.eatTrivia()
-
if Theory.precondition(&parser) { break }
-
-
if !Import.tryParse(&parser, recovery: recovery) {
-
parser.advance(error: "Expected to see either an import or a theory declaration, but instead got \(parser.currentToken.kind): \(parser.currentToken.text)")
-
}
-
}
-
-
// Theories section
-
while !parser.isAt(kind: .eof) {
-
if !Theory.tryParse(&parser, recovery: recovery) {
-
if parser.isAt(kindSatisfying: \.isVisible) {
-
let token = parser.currentToken
-
parser.advance(error: "Unexpected token: \(token.kind)")
-
} else {
-
parser.advance(metadata: nil)
-
}
-
}
-
parser.eatTrivia()
-
}
-
-
parser.eatTrivia()
-
_ = parser.eat(kind: .eof, metadata: nil)
-
-
return ParseResult(kind: Self.kind)
-
}
-
}
-
-
extension SyntaxView<Document> {
-
var imports: [SyntaxView<Import>] { matchingSubviews() }
-
var theories: [SyntaxView<Theory>] { matchingSubviews() }
-
}
-42
Sources/PterodactylSyntax/Grammar.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
/// This is an abstraction of grammatical productions.
-
public protocol Grammar: Sendable {
-
/// The kinds of tree that the production produces.
-
static var kinds: [SyntaxTreeKind] { get }
-
-
/// Indicates whether the current parser state is consistent with the grammatical production starting here. When a given grammatical element is optional, this can be used to avoid backtracking. This is a *precondition* for parsing.
-
static func precondition(_ parser: inout Parser) -> Bool
-
-
/// Parse the grammatical production, assuming the precondition indicated by ``precondition(_:)``. This function should not be called outside this module (instead, use ``parse(_:recovery:)`` and ``tryParse(_:recovery:)``.
-
static func inside(_ parser: inout Parser, recovery: Set<TokenKind>) -> ParseResult
-
}
-
-
extension Grammar {
-
public static func tryParse(_ parser: inout Parser, recovery: Set<TokenKind>) -> Bool {
-
guard !parser.isEndOfFile && precondition(&parser) else { return false }
-
parse(&parser, recovery: recovery)
-
return true
-
}
-
-
public static func parse(_ parser: inout Parser, recovery: Set<TokenKind>) {
-
let mark = parser.builder.open()
-
let result = inside(&parser, recovery: recovery)
-
parser.builder.close(mark: mark, kind: result.kind, metadata: result.metadata)
-
}
-
}
-
-
public struct ParseResult {
-
public var kind: SyntaxTreeKind
-
public var metadata: SyntaxTreeMetadata? = nil
-
-
public init(kind: SyntaxTreeKind, metadata: SyntaxTreeMetadata? = nil) {
-
self.kind = kind
-
self.metadata = metadata
-
}
-
}
-
-42
Sources/PterodactylSyntax/ImportParser.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
public struct ImportParser {
-
private var lexer: PterodactylSyntax.Lexer
-
public private(set) var imports: [String] = []
-
-
public init(input: String) {
-
self.lexer = PterodactylSyntax.Lexer(input: input)
-
}
-
-
public mutating func parseHeader() {
-
while true {
-
guard let token = nextSignificantToken() else { return }
-
switch token.kind {
-
case .keyword(.import): parseImportStatement()
-
default: return
-
}
-
}
-
}
-
-
/// Returns the next non-whitespace token.
-
private mutating func nextSignificantToken() -> Token? {
-
var token = lexer.nextToken()
-
while token?.kind.isTrivia == true {
-
token = lexer.nextToken()
-
}
-
-
guard let token else { return nil }
-
return Token(kind: token.kind, text: token.text)
-
}
-
-
/// Parses a single `import xyz` line.
-
private mutating func parseImportStatement() {
-
guard let next = nextSignificantToken() else { return }
-
guard next.kind == .identifier else { return }
-
imports.append(next.text)
-
}
-
}
-138
Sources/PterodactylSyntax/Lexer.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
/// This tokenises a string, without handling block layout at all. The tokens produced here should be fed into the ``BlockLayoutProcessor``.
-
public struct Lexer {
-
private let input: String
-
private var index: String.Index
-
private var tokens: [PterodactylSyntax.Token]
-
-
public init(input: String) {
-
self.input = input
-
self.index = input.startIndex
-
self.tokens = []
-
}
-
-
private var isAtEnd: Bool { index >= input.endIndex }
-
-
private var peek: Character? {
-
guard index < input.endIndex else { return nil }
-
return input[index]
-
}
-
-
private func lookahead() -> Character? {
-
guard index < input.endIndex else { return nil }
-
let next = input.index(after: index)
-
guard next < input.endIndex else { return nil }
-
return input[next]
-
}
-
-
private mutating func advance() -> Character {
-
let c = input[index]
-
index = input.index(after: index)
-
-
return c
-
}
-
-
private mutating func consume(while predicate: (Character) -> Bool) {
-
while let c = peek, predicate(c) {
-
_ = advance()
-
}
-
}
-
-
func text(from start: String.Index) -> String {
-
let range = start..<index
-
return String(input[range])
-
}
-
-
public mutating func nextToken() -> (kind: TokenKind, text: String)? {
-
guard let c = peek else {
-
return nil
-
}
-
-
let start = index
-
-
if c.isNewline {
-
_ = advance()
-
return (kind: .newline, text: text(from: start))
-
}
-
-
if c.isWhitespace && !c.isNewline {
-
consume { $0.isWhitespace && !$0.isNewline }
-
return (kind: .whitespace, text: text(from: start))
-
}
-
-
if c == "/" && lookahead() == "/" {
-
_ = advance()
-
_ = advance()
-
consume { $0 != "\n" }
-
return (kind: .lineComment, text: text(from: start))
-
}
-
-
if c == "/" && lookahead() == "*" {
-
_ = advance() // consume '/'
-
_ = advance() // consume '*'
-
var terminated = false
-
-
while let ch = peek {
-
if ch == "*" && lookahead() == "/" {
-
_ = advance() // consume '*'
-
_ = advance() // consume '/'
-
terminated = true
-
break
-
}
-
_ = advance()
-
}
-
-
return (kind: .blockComment(terminated: terminated), text: text(from: start))
-
}
-
-
if c == "<" && lookahead() == "=" {
-
_ = advance()
-
_ = advance()
-
return (kind: .punctuation(.doubleLeftArrow), text: text(from: start))
-
}
-
-
if c == "=" && lookahead() == ">" {
-
_ = advance()
-
_ = advance()
-
return (kind: .punctuation(.doubleRightArrow), text: text(from: start))
-
}
-
-
if let punct = Punctuation(rawValue: String(c)) {
-
_ = advance()
-
return (.punctuation(punct), String(c))
-
} else if c.isLetter || c == "_" {
-
_ = advance()
-
consume { $0.isLetter || $0.isNumber || $0 == "_" }
-
let text = text(from: start)
-
if let keyword = Keyword(rawValue: text) {
-
return (kind: .keyword(keyword), text: text)
-
}
-
return (kind: .identifier, text: text)
-
}
-
-
// Invalid single char (donโ€™t drop input)
-
let ch = advance()
-
return (kind: .error, text: String(ch))
-
}
-
-
public mutating func tokenize() -> [Token] {
-
var tokens: [Token] = []
-
-
while !isAtEnd {
-
guard let token = nextToken() else { break }
-
tokens.append(
-
Token(kind: token.kind, text: token.text)
-
)
-
}
-
-
let eofToken = Token(kind: .eof, text: "")
-
tokens.append(eofToken)
-
-
return tokens
-
}
-
}
-32
Sources/PterodactylSyntax/LineMap.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Algorithms
-
import Foundation
-
-
public struct LineMap: Codable, Sendable {
-
private let utf16LineOffsets: [Int]
-
-
public init(source: String) {
-
var offsets: [Int] = [0]
-
for idx in source.indices {
-
let c = source[idx]
-
if c == "\n" || c == "\r\n" || c == "\r" {
-
let next = source.index(after: idx)
-
let utf16Offset = next.utf16Offset(in: source)
-
offsets.append(utf16Offset)
-
}
-
}
-
self.utf16LineOffsets = offsets
-
}
-
-
public func location(at utf16Offset: Int) -> (line: Int, column: Int) {
-
let partitioningIndex = utf16LineOffsets.partitioningIndex { $0 > utf16Offset }
-
let lineIndex = partitioningIndex == 0 ? 0 : partitioningIndex - 1
-
let lineStart = utf16LineOffsets[lineIndex]
-
let lineNumber = lineIndex
-
let columnNumber = utf16Offset - lineStart
-
return (lineNumber, columnNumber)
-
}
-
}
-166
Sources/PterodactylSyntax/Parser.swift
···
-
// 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<Int> {
-
return absoluteUtf16Offset..<absoluteUtf16Offset
-
}
-
-
public var absoluteRangeOfCurrentToken: Range<Int> {
-
return absoluteUtf16Offset..<absoluteUtf16Offset + currentToken.utf16Length
-
}
-
-
public var isEndOfFile: Bool {
-
position == tokens.count
-
}
-
-
public var currentToken: Token {
-
if tokens.indices.contains(position) {
-
return tokens[position]
-
} else {
-
return Token(
-
kind: .eof,
-
text: "",
-
)
-
}
-
}
-
-
public func isAt(kind: TokenKind) -> 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<TokenKind>, 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..<endOffset))
-
}
-
}
-
}
-
-
public mutating func expect(kind: TokenKind, metadata: TokenMetadata?, recovery: Set<TokenKind>, 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() {}
-
}
-
}
-91
Sources/PterodactylSyntax/SemanticToken.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import LanguageServerProtocol
-
-
extension SemanticTokenTypes {
-
public var index: Int {
-
Self.allCases.firstIndex(of: self)!
-
}
-
}
-
-
extension SemanticTokenModifiers {
-
private static let modifierBitPositions: [SemanticTokenModifiers: Int] = {
-
var dict: [SemanticTokenModifiers: Int] = [:]
-
for (i, modifier) in SemanticTokenModifiers.allCases.enumerated() {
-
dict[modifier] = i
-
}
-
return dict
-
}()
-
-
static func encodeBitset(_ modifiers: Set<SemanticTokenModifiers>) -> UInt32 {
-
var bitset: UInt32 = 0
-
for modifier in modifiers {
-
if let bit = modifierBitPositions[modifier] {
-
bitset |= (1 << bit)
-
}
-
}
-
return bitset
-
}
-
}
-
-
-
struct SingleLineRange {
-
let line: Int
-
let char: Int
-
let length: Int
-
}
-
-
-
extension TokenMetadata {
-
func semanticToken(range: SingleLineRange) -> SemanticToken? {
-
guard range.length > 0 else { return nil }
-
return SemanticToken(
-
line: UInt32(range.line),
-
char: UInt32(range.char),
-
length: UInt32(range.length),
-
type: UInt32(semanticTokenType.index),
-
modifiers: SemanticTokenModifiers.encodeBitset(semanticTokenModifiers)
-
)
-
}
-
}
-
-
extension SyntaxCursor {
-
var singleLineRanges: [SingleLineRange] {
-
var result: [SingleLineRange] = []
-
var location = lineMap.location(at: utf16Offset)
-
-
for line in node.text.split(omittingEmptySubsequences: false, whereSeparator: \.isNewline) {
-
let length = line.utf16.count
-
result.append(SingleLineRange(line: location.line, char: location.column, length: length))
-
location.line += 1
-
location.column = 0
-
}
-
-
return result
-
}
-
}
-
-
extension SyntaxCursor {
-
public func collectSemanticTokens(_ sink: inout [SemanticToken]) {
-
if let (_, metadata) = node.token, let metadata {
-
for lineRange in singleLineRanges {
-
if let semanticToken = metadata.semanticToken(range: lineRange) {
-
sink.append(semanticToken)
-
}
-
}
-
}
-
-
for child in children {
-
child.collectSemanticTokens(&sink)
-
}
-
}
-
-
public var semanticTokens: [SemanticToken] {
-
var tokens: [SemanticToken] = []
-
collectSemanticTokens(&tokens)
-
return tokens
-
}
-
}
-51
Sources/PterodactylSyntax/SyntaxCursor.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
import LanguageServerProtocol
-
-
/// This is a โ€œred treeโ€ in the sense of Roslyn. In essence it instruments syntax trees with non-relative location information.
-
public final class SyntaxCursor {
-
public let lineMap: LineMap
-
public let node: SyntaxTree.Child
-
public let utf16Offset: Int
-
-
public private(set) lazy var children: [SyntaxCursor] = {
-
var children: [SyntaxCursor] = []
-
var utf16Offset = utf16Offset
-
for childNode in node.children {
-
children.append(Self(lineMap: lineMap, node: childNode, utf16Offset: utf16Offset))
-
utf16Offset += childNode.utf16Length
-
}
-
-
return children
-
}()
-
-
public var utf16Range: Range<Int> {
-
utf16Offset..<utf16Offset + node.utf16Length
-
}
-
-
public init(lineMap: LineMap, node: SyntaxTree.Child, utf16Offset: Int) {
-
self.lineMap = lineMap
-
self.node = node
-
self.utf16Offset = utf16Offset
-
}
-
}
-
-
extension SyntaxCursor {
-
public func firstChild<T>(mapping: (SyntaxCursor) -> T?) -> T? {
-
for child in children {
-
if let result = mapping(child) {
-
return result
-
} else {
-
continue
-
}
-
}
-
return nil
-
}
-
-
public func children<T>(mapping: (SyntaxCursor) -> T?) -> [T] {
-
children.compactMap(mapping)
-
}
-
}
+8 -36
Sources/PterodactylSyntax/SyntaxTree.swift
···
import Foundation
-
/// This is a โ€œgreen treeโ€ in the sense of Roslyn.
public struct SyntaxTree: Codable, Sendable {
public let kind: SyntaxTreeKind
public let metadata: SyntaxTreeMetadata?
public let children: [Child]
public let utf16Length: Int
-
-
public enum Child: Codable, Sendable {
-
case token(Token, metadata: TokenMetadata?)
-
case tree(SyntaxTree)
-
}
+
+
public enum Child: Codable, Sendable {
+
case token(Token, metadata: TokenMetadata?)
+
case tree(SyntaxTree)
+
}
-
public init(kind: SyntaxTreeKind, metadata: SyntaxTreeMetadata? = nil, children: [SyntaxTree.Child]) {
+
public init(kind: SyntaxTreeKind, metadata: SyntaxTreeMetadata?, children: [SyntaxTree.Child]) {
self.kind = kind
self.metadata = metadata
self.children = children
···
}
extension SyntaxTree {
-
/// A mutable version of ``SyntaxTree`` that does not keep track of textual length, for use when constructing trees.
-
public struct MutableTree {
-
public var kind: SyntaxTreeKind
-
public var metadata: SyntaxTreeMetadata?
-
public var children: [Child]
-
-
var tree: SyntaxTree {
-
SyntaxTree(kind: kind, metadata: metadata, children: children)
-
}
-
}
-
}
-
-
extension SyntaxTree {
public var text: String {
children.map(\.text).joined()
}
}
extension SyntaxTree.Child {
-
public var text: String {
+
var text: String {
switch self {
case let .token(tok, _): tok.text
case let .tree(tree): tree.text
}
}
-
-
public var tree: SyntaxTree? {
-
switch self {
-
case let .tree(tree): tree
-
default: nil
-
}
-
}
-
var token: (Token, TokenMetadata?)? {
-
switch self {
-
case let .token(token, metadata): (token, metadata)
-
default: nil
-
}
-
}
-
var utf16Length: Int {
switch self {
case let .token(token, _): token.utf16Length
case let .tree(tree): tree.utf16Length
}
}
-
+
var children: [Self] {
switch self {
case .token: []
-70
Sources/PterodactylSyntax/SyntaxTreeBuilder.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
public struct SyntaxTreeBuilder {
-
private enum Event: Equatable {
-
case open(kind: SyntaxTreeKind, metadata: SyntaxTreeMetadata?)
-
case close
-
case advance(token: Token, metadata: TokenMetadata?)
-
}
-
-
public struct MarkOpened {
-
internal let index: Int
-
}
-
-
private var events: [Event] = []
-
-
public mutating func advance(token: Token, metadata: TokenMetadata?) {
-
events.append(.advance(token: token, metadata: metadata))
-
}
-
-
public mutating func open() -> MarkOpened {
-
let mark = MarkOpened(index: events.count)
-
events.append(.open(kind: .error, metadata: nil))
-
return mark
-
}
-
-
public mutating func close(mark: MarkOpened, kind: SyntaxTreeKind, metadata: SyntaxTreeMetadata?) {
-
events[mark.index] = .open(kind: kind, metadata: metadata)
-
events.append(.close)
-
}
-
-
public var tree: SyntaxTree {
-
var events = events
-
var stack: [SyntaxTree.MutableTree] = []
-
-
precondition(events.popLast() == .close)
-
-
for event in events {
-
switch event {
-
case .open(let kind, let metadata):
-
stack.append(SyntaxTree.MutableTree(kind: kind, metadata: metadata, children: []))
-
case .close:
-
let tree = stack.popLast()!
-
stack.modifyLast { last in
-
last.children.append(.tree(tree.tree))
-
}
-
case .advance(let token, let metadata):
-
stack.modifyLast { last in
-
last.children.append(.token(token, metadata: metadata))
-
}
-
}
-
}
-
-
assert(stack.count == 1)
-
return stack.popLast()!.tree
-
}
-
}
-
-
-
extension Array {
-
fileprivate mutating func modifyLast(_ modifier: (inout Element) -> Void) {
-
if var last = popLast() {
-
modifier(&last)
-
append(last)
-
}
-
}
-
}
-22
Sources/PterodactylSyntax/SyntaxView.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
import Foundation
-
-
/// An abstract syntax view around a ``SyntaxCursor``. This is to be populated by extensions targetting specific `G`.
-
struct SyntaxView<G: Grammar> {
-
let cursor: SyntaxCursor
-
init?(_ cursor: SyntaxCursor) {
-
guard let kind = cursor.node.tree?.kind, G.kinds.contains(kind) else { return nil }
-
self.cursor = cursor
-
}
-
-
func matchingSubview<X: Grammar>() -> SyntaxView<X>? {
-
return cursor.firstChild(mapping: SyntaxView<X>.init)
-
}
-
-
func matchingSubviews<X: Grammar>() -> [SyntaxView<X>] {
-
return cursor.children(mapping: SyntaxView<X>.init)
-
}
-
}
+2 -2
Sources/PterodactylSyntax/Token.swift
···
import Foundation
-
public struct Token: Codable, Equatable {
+
public struct Token: Codable {
public let kind: TokenKind
public let text: String
public let utf16Length: Int
-
public init(kind: TokenKind, text: String) {
+
init(kind: TokenKind, text: String) {
self.kind = kind
self.text = text
self.utf16Length = text.utf16.count
+6 -82
Sources/PterodactylSyntax/Types.swift
···
// SPDX-License-Identifier: MPL-2.0
import Foundation
-
import LanguageServerProtocol
-
public enum Keyword: String, Codable, Sendable, CaseIterable {
-
case theory = "theory"
-
case `where` = "where"
-
case `import` = "import"
-
}
-
-
public enum Punctuation: String, CaseIterable, Codable, Equatable, Sendable {
-
case lparen = "("
-
case rparen = ")"
-
case lbrace = "{"
-
case rbrace = "}"
-
case comma = ","
-
case dot = "."
-
case colon = ":"
-
case doubleLeftArrow = "<="
-
case doubleRightArrow = "=>"
-
case equal = "="
-
}
-
-
public enum TokenKind: Codable, Equatable, Sendable, Hashable {
-
case eof
-
case keyword(Keyword)
-
case punctuation(Punctuation)
-
case error
-
case identifier
-
case newline
-
case whitespace
-
case blockBegin
-
case blockEnd
-
case blockSep
-
case lineComment
-
case blockComment(terminated: Bool)
-
}
-
-
extension TokenKind {
-
public var isTrivia: Bool {
-
switch self {
-
case .whitespace, .newline, .lineComment, .blockComment: true
-
default: false
-
}
-
}
-
-
public var isVisible: Bool {
-
switch self {
-
case .whitespace, .blockBegin, .blockSep, .blockEnd, .newline: false
-
default: true
-
}
-
}
-
-
public var canDetermineLayoutColumn: Bool {
-
switch self {
-
case .whitespace, .eof: false
-
default: true
-
}
-
}
-
-
public var isBlockHerald: Bool {
-
switch self {
-
case .keyword(.where): true
-
default: false
-
}
-
}
-
+
public enum TokenKind: Codable, Equatable, Sendable {
+
case eof
}
-
public final class SyntaxTreeKind: Codable, Equatable, Sendable {
-
static let error: SyntaxTreeKind = .init(name: "error")
-
-
public static func == (lhs: SyntaxTreeKind, rhs: SyntaxTreeKind) -> Bool {
-
lhs === rhs
-
}
-
-
let name: String
-
var description: String { name }
-
-
required init(name: String) {
-
self.name = name
-
}
+
public enum SyntaxTreeKind: Codable, Equatable, Sendable {
+
case error
}
-
public struct TokenMetadata: Equatable, Codable, Sendable {
-
public var semanticTokenType: SemanticTokenTypes
-
public var semanticTokenModifiers: Set<SemanticTokenModifiers> = []
-
public var delimitedFoldingRangeKind: FoldingRangeKind? = nil
+
public struct TokenMetadata: Codable, Equatable, Sendable {
}
public struct SyntaxTreeMetadata: Codable, Equatable, Sendable {
-
public var delimitedFoldingRangeKind: FoldingRangeKind? = nil
+
}
+24
Sources/PterodactylSyntax/Utf16Position.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import Foundation
+
+
public struct Utf16Position: Equatable, Comparable, Sendable {
+
public var absoluteOffset: Int
+
public var line: Int
+
public var column : Int
+
public init(absoluteOffset: Int, line: Int, column: Int) {
+
self.absoluteOffset = absoluteOffset
+
self.line = line
+
self.column = column
+
}
+
+
public static func < (lhs: Utf16Position, rhs: Utf16Position) -> Bool {
+
lhs.absoluteOffset < rhs.absoluteOffset
+
}
+
}
+
+
public extension Utf16Position {
+
static var zero : Self { Self(absoluteOffset: 0, line: 0, column: 0) }
+
}
+5 -42
Tests/PterodactylBuildTests/Test.swift
···
import Testing
@testable import PterodactylBuild
-
@testable import PterodactylSyntax
@testable import llbuild2fx
struct BuildTests {
@Test
-
func testBlockLayout() async throws {
-
let code = """
-
theory Foo where
-
foo : bar
-
baz : sdf
-
"""
-
-
var lexer = PterodactylSyntax.Lexer(input: code)
-
let flatTokens = lexer.tokenize()
-
let blockTokens = BlockLayoutProcessor(tokens: flatTokens).layout()
-
-
#expect(
-
blockTokens.map(\.kind) == [
-
.keyword(.theory), .whitespace, .identifier, .whitespace, .keyword(.where), .blockBegin, .newline, .whitespace, .blockSep, .identifier, .whitespace,
-
.punctuation(.colon),
-
.whitespace, .identifier, .newline, .whitespace, .blockSep, .identifier, .whitespace, .punctuation(.colon), .whitespace, .identifier, .blockEnd, .eof
-
])
-
}
-
-
@Test
-
func testParse() async throws {
-
let code = """
-
theory Foo where
-
asdf : asdf
-
"""
-
-
var lexer = PterodactylSyntax.Lexer(input: code)
-
let flatTokens = lexer.tokenize()
-
let blockTokens = BlockLayoutProcessor(tokens: flatTokens).layout()
-
var parser = Parser(source: code, tokens: blockTokens)
-
Document.parse(&parser, recovery: [])
-
-
#expect(parser.diagnostics.isEmpty)
-
}
-
-
@Test
func testImports() async throws {
let group = LLBMakeDefaultDispatchGroup()
let db = LLBInMemoryCASDatabase(group: group)
···
)
let treeID: LLBDataID = try await client.store(declTree, ctx).get()
-
let dependencyGraph = try await engine.build(key: Keys.SourceTree.GetDependencyGraph(sourceTreeId: treeID), ctx).get()
-
let foo = UnitName(basename: "foo")
-
let bar = UnitName(basename: "bar")
-
let baz = UnitName(basename: "baz")
+
let dependencyGraph = try await engine.build(key: Keys.DependencyGraphOfSourceTree(sourceTreeId: treeID), ctx).get()
+
let foo = UnitName(name: "foo")
+
let bar = UnitName(name: "bar")
+
let baz = UnitName(name: "baz")
#expect(
dependencyGraph.edges == [
···
]
)
-
let dependenciesOfBaz = try await engine.build(key: Keys.SourceTree.GetDependencies(sourceTreeId: treeID, unitName: baz), ctx).get()
+
let dependenciesOfBaz = try await engine.build(key: Keys.TransitiveDependencies(sourceTreeId: treeID, unitName: baz), ctx).get().dependencies
#expect(dependenciesOfBaz == [foo, bar])
return
}
-36
Tests/PterodactylLanguageServerTests/Test.swift
···
-
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
-
//
-
// SPDX-License-Identifier: MPL-2.0
-
-
//
-
// File.swift
-
// Pterodactyl
-
//
-
// Created by Jon Sterling on 30/11/2025.
-
//
-
-
import Foundation
-
import Testing
-
-
@testable import PterodactylBuild
-
@testable import PterodactylLanguageServer
-
@testable import TSCBasic
-
@testable import llbuild2fx
-
-
struct LanguageServerTests {
-
@Test
-
func testSingletonFileTree() throws {
-
let path = try AbsolutePath(validating: "/foo/bar/file.txt")
-
let foo = LLBDeclFileTree.file(absolutePath: path, contents: "foobar")
-
let expected: LLBDeclFileTree =
-
.dir([
-
"foo": .dir([
-
"bar": .dir([
-
"file.txt": .file("foobar")
-
])
-
])
-
])
-
-
#expect(foo.debugDescription == expected.debugDescription)
-
}
-
}
-4
license.sh
···
-
#!/bin/bash
-
-
reuse annotate --recursive --license MPL-2.0 --copyright "The Project Pterodactyl Developers" Tests Sources
-
-6
test.ptero
···
-
theory asdf where
-
foo : asdfs
-
foo : sdf
-
-
/* asdfasdf */
-
// asdf;lkj asdf;klj asdfkjh asdfjlkha sdfljk