Compare changes

Choose any two refs to compare.

+1 -1
.gitignore
···
#
# SPDX-License-Identifier: MPL-2.0
+
.jj/*
.DS_Store
/.build
/Packages
···
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
-
Package.resolved
+159
Package.resolved
···
+
{
+
"originHash" : "ed464b5a4d4d87a0158db3738aa249b07410a79ce65ef2fa9d16a0b168de3b32",
+
"pins" : [
+
{
+
"identity" : "jsonrpc",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/ChimeHQ/JSONRPC",
+
"state" : {
+
"revision" : "29987f721374f30e686af40ccffd0b13b14dde1f",
+
"version" : "0.9.2"
+
}
+
},
+
{
+
"identity" : "swift-algorithms",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-algorithms",
+
"state" : {
+
"revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023",
+
"version" : "1.2.1"
+
}
+
},
+
{
+
"identity" : "swift-async-algorithms",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-async-algorithms.git",
+
"state" : {
+
"revision" : "6c050d5ef8e1aa6342528460db614e9770d7f804",
+
"version" : "1.1.1"
+
}
+
},
+
{
+
"identity" : "swift-atomics",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-atomics.git",
+
"state" : {
+
"revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7",
+
"version" : "1.3.0"
+
}
+
},
+
{
+
"identity" : "swift-collections",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-collections.git",
+
"state" : {
+
"revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e",
+
"version" : "1.3.0"
+
}
+
},
+
{
+
"identity" : "swift-distributed-tracing",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-distributed-tracing",
+
"state" : {
+
"revision" : "baa932c1336f7894145cbaafcd34ce2dd0b77c97",
+
"version" : "1.3.1"
+
}
+
},
+
{
+
"identity" : "swift-languageserver",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/jonsterling/swift-LanguageServer",
+
"state" : {
+
"branch" : "main",
+
"revision" : "8837131a4b8707a466d5a0fe1d122a566c73f3e1"
+
}
+
},
+
{
+
"identity" : "swift-languageserverprotocol",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/jonsterling/swift-LanguageServerProtocol",
+
"state" : {
+
"branch" : "main",
+
"revision" : "4dfba8054ae50840798242639afffa3411c93699"
+
}
+
},
+
{
+
"identity" : "swift-llbuild2",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-llbuild2.git",
+
"state" : {
+
"branch" : "main",
+
"revision" : "fa72f6a074360c6eb707ca53455874f4c0c4852f"
+
}
+
},
+
{
+
"identity" : "swift-log",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-log.git",
+
"state" : {
+
"revision" : "bc386b95f2a16ccd0150a8235e7c69eab2b866ca",
+
"version" : "1.8.0"
+
}
+
},
+
{
+
"identity" : "swift-nio",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-nio.git",
+
"state" : {
+
"revision" : "a1605a3303a28e14d822dec8aaa53da8a9490461",
+
"version" : "2.92.0"
+
}
+
},
+
{
+
"identity" : "swift-numerics",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-numerics.git",
+
"state" : {
+
"revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2",
+
"version" : "1.1.1"
+
}
+
},
+
{
+
"identity" : "swift-protobuf",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-protobuf.git",
+
"state" : {
+
"revision" : "c169a5744230951031770e27e475ff6eefe51f9d",
+
"version" : "1.33.3"
+
}
+
},
+
{
+
"identity" : "swift-service-context",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-service-context.git",
+
"state" : {
+
"revision" : "1983448fefc717a2bc2ebde5490fe99873c5b8a6",
+
"version" : "1.2.1"
+
}
+
},
+
{
+
"identity" : "swift-system",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-system.git",
+
"state" : {
+
"revision" : "395a77f0aa927f0ff73941d7ac35f2b46d47c9db",
+
"version" : "1.6.3"
+
}
+
},
+
{
+
"identity" : "swift-tools-support-async",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-tools-support-async.git",
+
"state" : {
+
"revision" : "8cd215778056b7baab3cd9e57ba03559c02f480a",
+
"version" : "0.17.0"
+
}
+
},
+
{
+
"identity" : "swift-tools-support-core",
+
"kind" : "remoteSourceControl",
+
"location" : "https://github.com/apple/swift-tools-support-core.git",
+
"state" : {
+
"revision" : "e8fbc8b05a155f311b862178d92d043afb216fe3",
+
"version" : "0.7.3"
+
}
+
}
+
],
+
"version" : 3
+
}
+35 -2
Package.swift
···
.library(
name: "PterodactylBuild",
targets: ["PterodactylBuild"]
+
),
+
.executable(
+
name: "PterodactylLanguageServer",
+
targets: ["PterodactylLanguageServer"]
)
],
dependencies: [
-
.package(url: "https://github.com/apple/swift-llbuild2.git", branch: "main")
+
.package(url: "https://github.com/jonsterling/swift-LanguageServer", branch: "main"),
+
.package(url: "https://github.com/jonsterling/swift-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")
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
···
name: "PterodactylKernel",
),
.target(
-
name: "PterodactylSyntax"
+
name: "PterodactylSyntax",
+
dependencies: [
+
.product(name: "Algorithms", package: "swift-algorithms"),
+
.product(name: "LanguageServerProtocol", package: "swift-LanguageServerProtocol")
+
]
),
.target(
name: "PterodactylBuild",
dependencies: [
+
"PterodactylSyntax",
.product(name: "llbuild2fx", package: "swift-llbuild2")
]
),
+
.executableTarget(
+
name: "PterodactylLanguageServer",
+
dependencies: [
+
.product(name: "LanguageServer", package: "swift-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 -10
Sources/PterodactylBuild/BuildContext.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
import TSCBasic
import llbuild2fx
···
}
extension BuildKey {
-
func computeValue(_ fi: FXFunctionInterface<Self>, _ ctx: Context) async throws -> ValueType {
+
public func computeValue(_ fi: FXFunctionInterface<Self>, _ ctx: Context) async throws -> ValueType {
try await computeValue(BuildContext(functionInterface: fi, context: ctx))
}
}
extension LLBCASFileTree {
-
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 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()
}
-
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 remove<X: FXKey>(path: AbsolutePath, in ctx: BuildContext<X>) async throws -> LLBCASFileTree {
+
try await remove(path: path, in: ctx.db, ctx.context).get()
+
}
-
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)
-
}
+
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)
+
}
}
+15
Sources/PterodactylBuild/FXValue+Conformances.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
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
-
}
-
}
-
}
+24
Sources/PterodactylBuild/Keys/Blob/GetLineMap.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
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)
+
}
+
}
+
}
+33
Sources/PterodactylBuild/Keys/Blob/ParseDocument.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
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)
+
var recoveryHandle = parser.recoveryStack.push([.lineComment, .blockComment(terminated: true), .blockComment(terminated: false)])
+
defer { recoveryHandle.close() }
+
parser.document()
+
return ValueType(tree: parser.tree, diagnostics: parser.diagnostics)
+
}
+
}
+
}
+30
Sources/PterodactylBuild/Keys/Blob/ParseImports.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
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)
+
}
+
}
+
}
+22
Sources/PterodactylBuild/Keys/Blob/Tokenise.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
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)
+
return lexer.tokenize()
+
}
+
}
+
}
-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)
-
}
-
}
-
}
+21
Sources/PterodactylBuild/Keys/SourceTree/GetDependencies.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
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)
+
}
+
}
+
}
+30
Sources/PterodactylBuild/Keys/SourceTree/GetDependencyGraph.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
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)
+
}
+
}
+
}
+24
Sources/PterodactylBuild/Keys/SourceTree/GetUnitMap.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
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)
+
}
+
}
+
}
+31
Sources/PterodactylBuild/Keys/SourceTree/NarrowToUnit.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
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)
-
}
-
}
-
}
+9
Sources/PterodactylBuild/Keys.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
+
public enum Keys {
+
public enum Blob {}
+
public enum SourceTree {}
+
}
-1
Sources/PterodactylBuild/LLBCASFileTree+Traversal.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
import TSCBasic
import llbuild2fx
-1
Sources/PterodactylBuild/Types/Graph.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
struct Graph<Vertex: Hashable> {
var edges: [Vertex: Set<Vertex>]
-1
Sources/PterodactylBuild/Types/UnitInfo.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
import TSCBasic
import llbuild2fx
-1
Sources/PterodactylBuild/Types/UnitMap.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
import llbuild2fx
struct UnitMap: Codable, FXValue {
+5 -6
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 name: String
+
var basename: String
}
extension UnitName: FXValue {}
extension UnitName {
-
static func fromPath(_ path: AbsolutePath) -> Self {
-
Self(name: path.basenameWithoutExt)
-
}
+
static func fromPath(_ path: AbsolutePath) -> Self {
+
Self(basename: path.basenameWithoutExt)
+
}
}
-1
Sources/PterodactylKernel/Control/AsyncThunk.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
public actor AsyncThunk<Value> {
private var storage: Value?
-1
Sources/PterodactylKernel/Core Types/FieldDict.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
// TODO: This might be rewritten. The goal is to provide a dictionary that I can easily construct and traverse in order. Maybe ordered dictionaries from swift-collections?
public struct FieldDict<Value> {
-1
Sources/PterodactylKernel/Core Types/Size.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
/// Pterodactyl has two sizes of small things: ``little(universe:)`` and ``big``. Beyond these, we have things that are not types but rather โ€œtype schemesโ€.
public enum Size<Universe> {
-1
Sources/PterodactylKernel/Local Analysis/Equality.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
extension LocalAnalysis {
enum EqualityError: Error {
-1
Sources/PterodactylKernel/Local Analysis/LocalAnalysis.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
struct LocalAnalysis {
let depth: Int
-1
Sources/PterodactylKernel/Local Analysis/Quotation.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
extension LocalAnalysis {
func quote(type: Value.Type_) -> Term.Type_ {
-1
Sources/PterodactylKernel/Smallness.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
extension LocalAnalysis {
struct SizeError: Error { }
+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)
+
}
+
}
+16
Sources/PterodactylLanguageServer/Archive/LLBDeclFileTree+Singleton.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import TSCBasic
+
import llbuild2fx
+
+
extension LLBDeclFileTree {
+
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])
+
}
+
}
+
}
+12
Sources/PterodactylLanguageServer/Event Handlers/ErrorHandler.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import LanguageServer
+
import Logging
+
+
struct PterodactylServerErrorHandler: LanguageServer.ErrorHandler {
+
func internalError(_ error: any Error) async {
+
Logger.shared.error("Received error: \(error)")
+
}
+
}
+56
Sources/PterodactylLanguageServer/Event Handlers/NotificationHandler.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import Foundation
+
import LanguageServer
+
import LanguageServerProtocol
+
import Logging
+
import PterodactylBuild
+
import llbuild2fx
+
+
struct PterodactylServerNotificationHandler: LanguageServer.NotificationHandler {
+
private let actor: PterodactylServerActor
+
init(actor: PterodactylServerActor) {
+
self.actor = actor
+
}
+
+
func internalError(_ error: any Error) async {
+
Logger.shared.error("Notification handler received error: \(error)")
+
}
+
+
func textDocumentDidChange(_ params: DidChangeTextDocumentParams) async {
+
guard let text = params.contentChanges.first?.text else { return }
+
do {
+
let blobId: LLBDataID = try await actor.storeBlob(text: text, uri: params.textDocument.uri, version: params.textDocument.version)
+
try await actor.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 actor.storeBlob(text: text, uri: params.textDocument.uri, version: params.textDocument.version)
+
try await actor.publishLiveDiagnostics(blobId: blobId, uri: params.textDocument.uri, version: params.textDocument.version)
+
} catch {}
+
}
+
+
func exit() async {
+
Logger.shared.info("Received exit notification")
+
Foundation.exit(0)
+
}
+
+
func initialized(_ params: InitializedParams) async {}
+
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 {}
+
}
+153
Sources/PterodactylLanguageServer/Event Handlers/RequestHandler.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import JSONRPC
+
import LanguageServer
+
import LanguageServerProtocol
+
import Logging
+
+
struct PterodactylServerRequestHandler: LanguageServer.RequestHandler {
+
private let actor: PterodactylServerActor
+
init(actor: PterodactylServerActor) {
+
self.actor = actor
+
}
+
+
func internalError(_ error: any Error) async {
+
Logger.shared.error("Request handler received error: \(error)")
+
}
+
+
// MARK: - Configurations
+
var textDocumentSyncOptions: 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,
+
range: .optionA(true)
+
)
+
}
+
+
var serverCapabilities: ServerCapabilities {
+
var serverCapabilities = ServerCapabilities()
+
serverCapabilities.textDocumentSync = .optionA(textDocumentSyncOptions)
+
serverCapabilities.completionProvider = completionOptions
+
serverCapabilities.hoverProvider = .optionA(false)
+
serverCapabilities.semanticTokensProvider = .optionA(semanticTokensOptions)
+
serverCapabilities.foldingRangeProvider = .optionA(true)
+
serverCapabilities.inlayHintProvider = .optionA(true)
+
return serverCapabilities
+
}
+
+
var serverInfo: ServerInfo {
+
ServerInfo(
+
name: "PterodactylLanguageServer",
+
version: nil
+
)
+
}
+
+
// MARK: - Responses
+
+
func initialize(id: JSONId, params: InitializeParams) async -> Response<InitializationResponse> {
+
Logger.shared.debug("Received initialize request")
+
let response = InitializationResponse(
+
capabilities: serverCapabilities,
+
serverInfo: serverInfo
+
)
+
return .success(response)
+
}
+
+
func semanticTokensFull(id: JSONId, params: SemanticTokensParams) async -> Response<SemanticTokensResponse> {
+
guard let tokens = await actor.semanticTokens(uri: params.textDocument.uri, in: nil) else { return .success(nil) }
+
return .success(SemanticTokens(resultId: nil, tokens: tokens))
+
}
+
+
func semanticTokensRange(id: JSONId, params: SemanticTokensRangeParams) async -> Response<SemanticTokensResponse> {
+
guard let tokens = await actor.semanticTokens(uri: params.textDocument.uri, in: params.range) else { return .success(nil) }
+
return .success(SemanticTokens(resultId: nil, tokens: tokens))
+
}
+
+
func foldingRange(id: JSONId, params: FoldingRangeParams) async -> Response<FoldingRangeResponse> {
+
guard let cursor = await actor.cursor(uri: params.textDocument.uri)?.cursor else { return .success(nil) }
+
return .success(FoldingRangeResponse(cursor.foldingRanges))
+
}
+
+
func inlayHint(id: JSONId, params: InlayHintParams) async -> Response<InlayHintResponse> {
+
guard let cursor = await actor.cursor(uri: params.textDocument.uri)?.cursor else { return .success(nil) }
+
let lineMap = cursor.lineMap
+
+
var hints: [(Range<Int>, String)] = []
+
+
let range = lineMap.range(from: params.range)
+
cursor.collectHints(sink: &hints, in: range)
+
+
let lspHints = hints.map { (range, hint) in
+
let position = lineMap.lspPosition(at: range.endIndex)
+
return InlayHint(position: position, label: .optionA(hint))
+
}
+
+
return .success(InlayHintResponse(lspHints))
+
}
+
+
func shutdown(id: JSONId) async {}
+
func workspaceInlayHintRefresh(id: JSONId) async {}
+
+
func inlayHintResolve(id: JSONId, params: InlayHint) async -> Response<InlayHintResponse> { 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 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 textDocumentWillSaveWaitUntil(id: JSONId, params: WillSaveTextDocumentParams) async -> Response<[TextEdit]?> { .success(nil) }
+
func completion(id: JSONId, params: CompletionParams) async -> Response<CompletionResponse> { .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 codeLens(id: JSONId, params: CodeLensParams) async -> Response<CodeLensResponse> { .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 documentLink(id: JSONId, params: DocumentLinkParams) async -> Response<DocumentLinkResponse> { .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 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
+
}()
+
}
+32
Sources/PterodactylLanguageServer/PterodactylServer.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import JSONRPC
+
import LanguageServer
+
import LanguageServerProtocol
+
import Logging
+
+
@main
+
struct PterodactylServer {
+
private let connection: JSONRPCClientConnection
+
private let dispatcher: LanguageServer.EventDispatcher
+
+
init() {
+
let channel = DataChannel.stdio()
+
connection = JSONRPCClientConnection(channel)
+
let languageServerActor = PterodactylServerActor(connection: connection)
+
dispatcher = LanguageServer.EventDispatcher(
+
connection: connection,
+
requestHandler: PterodactylServerRequestHandler(actor: languageServerActor),
+
notificationHandler: PterodactylServerNotificationHandler(actor: languageServerActor),
+
errorHandler: PterodactylServerErrorHandler()
+
)
+
}
+
+
static func main() async throws {
+
Logger.shared.debug("Starting!")
+
let server = Self()
+
await server.dispatcher.run()
+
}
+
}
+69
Sources/PterodactylLanguageServer/PterodactylServerActor.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import JSONRPC
+
import LanguageServer
+
import LanguageServerProtocol
+
import Logging
+
import PterodactylBuild
+
import PterodactylSyntax
+
import TSCBasic
+
import llbuild2fx
+
+
actor PterodactylServerActor {
+
private let connection: JSONRPCClientConnection
+
private let buildEngine: FXEngine
+
private let casContext: TSCUtility.Context
+
private let casClient: LLBCASFSClient
+
+
private 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))
+
}
+
+
func cursor(uri: DocumentUri) async -> SyntaxCursor.Options? {
+
guard let storedBlob = storedBlobs[uri] else { return 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()
+
return SyntaxCursor.Options(lineMap: lineMap, node: .tree(parseResult.tree), utf16Offset: 0)
+
} catch {
+
return nil
+
}
+
}
+
+
func semanticTokens(uri: DocumentUri, in lspRange: LSPRange?) async -> [SemanticToken]? {
+
guard let cursor = await cursor(uri: uri)?.cursor else { return nil }
+
let lineMap = cursor.lineMap
+
let range = lspRange.map { lineMap.range(from: $0 ) }
+
return cursor.semanticTokens(in: range)
+
}
+
}
-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
-
}
-
}
+37
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.lspPosition(at: absoluteUtf16Range.lowerBound)
+
let end = lineMap.lspPosition(at: absoluteUtf16Range.upperBound)
+
return LSPRange(start: start, end: end)
+
}
+
+
public func lspDiagnostic(lineMap: LineMap) -> LanguageServerProtocol.Diagnostic {
+
LanguageServerProtocol.Diagnostic(
+
range: lspRange(lineMap: lineMap),
+
severity: severity,
+
message: message
+
)
+
}
+
}
+61
Sources/PterodactylSyntax/FoldingRanges.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
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.lspPosition(at: visibleUtf16Range.lowerBound)
+
let endLocation = lineMap.lspPosition(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
+
}
+
}
+110
Sources/PterodactylSyntax/Grammar.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
+
extension SyntaxTreeKind {
+
static let hint = SyntaxTreeKind(name: "hint")
+
}
+
+
/// This is an abstraction of grammatical productions.
+
public protocol Grammar: Sendable {
+
/// The kinds of tree that the production produces.
+
static var kinds: [SyntaxTreeKind] { get }
+
}
+
+
public enum Document: Grammar {
+
public static let kind = SyntaxTreeKind(name: "document")
+
public static let kinds = [kind]
+
}
+
+
enum ImportName: Grammar {
+
static let kind = SyntaxTreeKind(name: "import.name")
+
static let kinds = [kind]
+
}
+
+
enum Import: Grammar {
+
static let kind = SyntaxTreeKind(name: "import")
+
static let kinds = [kind]
+
}
+
+
enum Theory: Grammar {
+
static let kind = SyntaxTreeKind(name: "theory")
+
static let kinds = [kind]
+
}
+
+
enum TheoryName: Grammar {
+
static let kind = SyntaxTreeKind(name: "theory.name")
+
static let kinds = [kind]
+
}
+
+
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]
+
}
+
+
extension Declaration {
+
enum Lhs: Grammar {
+
static let kind = SyntaxTreeKind(name: "declaration.lhs")
+
static let kinds = [kind]
+
}
+
+
enum Rhs: Grammar {
+
static let kind = SyntaxTreeKind(name: "declaration.rhs")
+
static let kinds = [kind]
+
}
+
}
+
+
enum TypedBinder: Grammar {
+
static let kind = SyntaxTreeKind(name: "typed-binder")
+
static let kinds = [kind]
+
}
+
+
enum DependentFunctionType: Grammar {
+
static let kind = SyntaxTreeKind(name: "pi-type")
+
static let kinds = [kind]
+
}
+
+
enum NondependentFunctionType: Grammar {
+
static let kind = SyntaxTreeKind(name: "fun-type")
+
static let kinds = [kind]
+
}
+
+
enum Application: Grammar {
+
static let kind = SyntaxTreeKind(name: "app")
+
static let kinds = [kind]
+
}
+
+
extension SyntaxView<Document> {
+
var imports: [SyntaxView<Import>] { matchingSubviews() }
+
var theories: [SyntaxView<Theory>] { matchingSubviews() }
+
}
+
+
extension SyntaxView<Declaration> {
+
var lhs: SyntaxView<Declaration.Lhs>? { matchingSubview() }
+
var rhs: SyntaxView<Declaration.Rhs>? { matchingSubview() }
+
}
+
+
extension SyntaxView<TheoryName> {
+
var text: String { cursor.node.text }
+
}
+
+
extension SyntaxView<Theory> {
+
var name: SyntaxView<TheoryName>? { matchingSubview() }
+
var decls: [SyntaxView<Declaration>] { matchingSubviews() }
+
}
+
+
extension SyntaxView<ImportName> {
+
var text: String { cursor.node.text }
+
}
+
+
extension SyntaxView<Import> {
+
var name: SyntaxView<ImportName>? {
+
matchingSubview()
+
}
+
}
+40
Sources/PterodactylSyntax/ImportParser.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
+
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 {
+
let token = nextSignificantToken()
+
switch token.kind {
+
case .keyword(.import): parseImportStatement()
+
default: return
+
}
+
}
+
}
+
+
/// Returns the next non-whitespace token.
+
private mutating func nextSignificantToken() -> Token {
+
var token = lexer.scan()
+
while token.kind.isTrivia == true {
+
token = lexer.scan()
+
}
+
+
return Token(kind: token.kind, text: token.text)
+
}
+
+
/// Parses a single `import xyz` line.
+
private mutating func parseImportStatement() {
+
let next = nextSignificantToken()
+
guard next.kind == .identifier else { return }
+
imports.append(next.text)
+
}
+
}
+263
Sources/PterodactylSyntax/Lexer.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
+
// This is based on a tutorial by Amรฉlia Liao: https://amelia.how/posts/parsing-layout.html
+
public struct Lexer {
+
enum Layout {
+
case explicit
+
case layoutColumn(Int)
+
}
+
+
enum Mode {
+
case normal
+
case layout
+
case emptyLayout
+
case newline
+
}
+
+
struct ModeStack {
+
private var stack: [Mode] = []
+
+
mutating func push(mode: Mode) {
+
stack.append(mode)
+
}
+
+
mutating func drop() {
+
_ = stack.popLast()
+
}
+
+
var current: Mode {
+
stack.last ?? .normal
+
}
+
}
+
+
private let input: String
+
private var index: String.Index
+
private var layoutStack: [Layout]
+
private var modeStack: ModeStack
+
private var inputColumn: Int
+
+
public init(input: String) {
+
self.input = input
+
self.index = input.startIndex
+
self.modeStack = ModeStack()
+
self.layoutStack = []
+
self.inputColumn = 0
+
}
+
+
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]
+
if c.isNewline {
+
inputColumn = 0
+
} else {
+
inputColumn += 1
+
}
+
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])
+
}
+
+
var isAtNewline: Bool {
+
peek?.isNewline ?? false
+
}
+
+
var isAtLineComment: Bool {
+
guard let c = peek else { return false }
+
return c == "/" && lookahead() == "/"
+
}
+
+
var isAtBlockComment: Bool {
+
guard let c = peek else { return false }
+
return c == "/" && lookahead() == "*"
+
}
+
+
mutating func scanNormal() -> Token {
+
guard let c = peek else { return eof() }
+
let start = index
+
if c.isWhitespace && !c.isNewline {
+
consume { $0.isWhitespace && !$0.isNewline }
+
return Token(kind: .whitespace, text: text(from: start))
+
} else if c == "<" && lookahead() == "=" {
+
_ = advance()
+
_ = advance()
+
return Token(kind: .punctuation(.doubleLeftArrow), text: text(from: start))
+
} else if c == "-" && lookahead() == ">" {
+
_ = advance()
+
_ = advance()
+
return Token(kind: .punctuation(.rightArrow), text: text(from: start))
+
} else if c == "=" && lookahead() == ">" {
+
_ = advance()
+
_ = advance()
+
return Token(kind: .punctuation(.doubleRightArrow), text: text(from: start))
+
} else if let punct = Punctuation(rawValue: String(c)) {
+
_ = advance()
+
return Token(kind: .punctuation(punct), text: 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) {
+
if keyword.isBlockHerald { modeStack.push(mode: .layout) }
+
return Token(kind: .keyword(keyword), text: text)
+
}
+
return Token(kind: .identifier, text: text)
+
} else if isAtNewline || isAtLineComment || isAtBlockComment {
+
modeStack.push(mode: .newline)
+
return scan()
+
} else {
+
let c = advance()
+
return Token(kind: .error, text: String(c))
+
}
+
}
+
+
mutating func scanLayout() -> Token {
+
if let token = scanNewlineOrComment() { return token }
+
return startLayout()
+
}
+
+
mutating func scanLineComment() -> Token {
+
let start = index
+
_ = advance() // consume '/'
+
_ = advance() // consume '/'
+
consume { $0 != "\n" }
+
return Token(kind: .lineComment, text: text(from: start))
+
}
+
+
mutating func scanBlockComment() -> Token {
+
let start = index
+
_ = advance() // consume '/'
+
_ = advance() // consume '*'
+
var terminated = false
+
+
while let ch = peek {
+
if ch == "*" && lookahead() == "/" {
+
_ = advance() // consume '*'
+
_ = advance() // consume '/'
+
terminated = true
+
break
+
}
+
_ = advance()
+
}
+
+
return Token(kind: .blockComment(terminated: terminated), text: text(from: start))
+
}
+
+
mutating func scanNewlineOrComment() -> Token? {
+
guard let c = peek else { return eof() }
+
if isAtNewline {
+
_ = advance()
+
return Token(kind: .newline, text: String(c))
+
} else if isAtLineComment {
+
return scanLineComment()
+
} else if isAtBlockComment {
+
return scanBlockComment()
+
} else {
+
return nil
+
}
+
}
+
+
mutating func scan() -> Token {
+
switch modeStack.current {
+
case .normal: return scanNormal()
+
case .layout: return scanLayout()
+
case .emptyLayout: return emptyLayout()
+
case .newline: return scanNewlineOrComment() ?? offsideRule()
+
}
+
}
+
+
mutating func eof() -> Token {
+
if layoutStack.last == nil {
+
modeStack.drop()
+
return Token(kind: .eof, text: "")
+
} else {
+
_ = layoutStack.popLast()
+
return Token(kind: .blockEnd, text: "")
+
}
+
}
+
+
mutating func startLayout() -> Token {
+
modeStack.drop()
+
+
let column = inputColumn
+
let reference = layoutStack.last
+
let layout = Layout.layoutColumn(column)
+
+
if let reference, layout <= reference {
+
modeStack.push(mode: .emptyLayout)
+
} else {
+
layoutStack.append(layout)
+
}
+
+
return Token(kind: .blockBegin, text: "")
+
}
+
+
mutating func emptyLayout() -> Token {
+
modeStack.drop()
+
modeStack.push(mode: .newline)
+
return Token(kind: .blockEnd, text: "")
+
}
+
+
mutating func offsideRule() -> Token {
+
let context = layoutStack.last
+
let column = inputColumn
+
+
switch context {
+
case .layoutColumn(let otherColumn):
+
if column == otherColumn {
+
modeStack.drop()
+
return Token(kind: .blockSep, text: "")
+
} else if column > otherColumn {
+
modeStack.drop()
+
return scan()
+
} else {
+
_ = layoutStack.popLast()
+
return Token(kind: .blockEnd, text: "")
+
}
+
default:
+
modeStack.drop()
+
return scan()
+
}
+
}
+
+
public mutating func tokenize() -> [Token] {
+
var tokens: [Token] = []
+
+
while true {
+
let token = scan()
+
+
tokens.append(token)
+
if token.kind == .eof {
+
return tokens
+
}
+
}
+
}
+
}
+
+
extension Lexer.Layout: Comparable {
+
}
+41
Sources/PterodactylSyntax/LineMap.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import Algorithms
+
import LanguageServerProtocol
+
+
public struct LineMap: Codable, Sendable {
+
private let utf16LineOffsets: [Int]
+
+
public init(source: String) {
+
var offsets: [Int] = [0]
+
for idx in source.indices {
+
if source[idx].isNewline {
+
let next = source.index(after: idx)
+
let utf16Offset = next.utf16Offset(in: source)
+
offsets.append(utf16Offset)
+
}
+
}
+
+
self.utf16LineOffsets = offsets
+
}
+
+
public func lspPosition(at utf16Offset: Int) -> LanguageServerProtocol.Position {
+
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 Position(line: lineNumber, character: columnNumber)
+
}
+
+
public func range(from lspRange: LSPRange) -> Range<Int> {
+
offset(at: lspRange.start)..<offset(at: lspRange.end)
+
}
+
+
public func offset(at position: LanguageServerProtocol.Position) -> Int {
+
let lineOffset = utf16LineOffsets[position.line]
+
return lineOffset + position.character
+
}
+
}
+269
Sources/PterodactylSyntax/ParseState.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
+
public enum ControlFlow<Value> {
+
case commit(Value)
+
case abort
+
}
+
+
extension ControlFlow: Equatable where Value: Equatable {}
+
+
extension ControlFlow where Value == Void {
+
static var commit: Self {
+
.commit(())
+
}
+
+
var isCommit: Bool {
+
switch self {
+
case .commit: true
+
default: false
+
}
+
}
+
}
+
+
+
public class RecoveryStack {
+
var stack: [Set<TokenKind>] = []
+
+
var current: Set<TokenKind> {
+
stack.last ?? []
+
}
+
+
public struct Handle: ~Copyable {
+
let parent: RecoveryStack
+
private var closed = false
+
+
init(parent: RecoveryStack, set: Set<TokenKind>) {
+
self.parent = parent
+
parent.stack.append(parent.current.union(set))
+
}
+
+
mutating public func close() {
+
precondition(!closed)
+
_ = parent.stack.popLast()
+
closed = true
+
}
+
+
deinit {
+
precondition(closed)
+
}
+
}
+
+
public func push(_ set: Set<TokenKind>) -> Handle {
+
return Handle(parent: self, set: set)
+
}
+
}
+
+
public struct Parser {
+
let source: String
+
let tokens: [Token]
+
+
public init(source: String, tokens: [Token]) {
+
self.source = source
+
self.tokens = tokens
+
}
+
+
public private(set) var diagnostics: [Diagnostic] = []
+
+
private let builder = SyntaxTreeBuilder()
+
public var tree: SyntaxTree { builder.tree }
+
+
public let recoveryStack = RecoveryStack()
+
+
private var inError: Bool = false
+
public private(set) 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 var nextSignificantToken: Token? {
+
guard tokens.indices.contains(position) else { return nil }
+
for index in position..<tokens.endIndex {
+
let token = tokens[index]
+
if !token.kind.isTrivia { return token }
+
}
+
+
return nil
+
}
+
+
public func isRoughlyAt(kind: TokenKind) -> Bool {
+
nextSignificantToken?.kind == kind
+
}
+
+
public func isAt(kind: TokenKind) -> Bool {
+
currentToken.kind == kind
+
}
+
+
public func isAt(kindSatisfying predicate: (TokenKind) -> Bool) -> Bool {
+
return predicate(currentToken.kind)
+
}
+
+
+
mutating public 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) {
+
var handle = builder.open()
+
defer { handle.close() }
+
let diagnostic = Diagnostic(
+
message: error,
+
absoluteRange: absoluteRangeOfCurrentToken
+
)
+
+
diagnostics.append(diagnostic)
+
advance(metadata: metadata)
+
}
+
+
public mutating func eat(kind: TokenKind, metadata: TokenMetadata?) -> Bool {
+
guard !isEndOfFile && isAt(kindSatisfying: { $0 == kind }) else { return false }
+
advance(metadata: metadata)
+
return true
+
}
+
+
+
private mutating func ate(kind: TokenKind, metadata: TokenMetadata?) -> Bool {
+
guard eat(kind: kind, metadata: metadata) else { return false }
+
inError = false
+
return true
+
}
+
+
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 message = error ?? "Expected one of \(expected.map(String.init(describing:)).sorted(using: .localized)) but got \(discardTokens)"
+
+
if discardTokens.isEmpty {
+
if !inError {
+
inError = true
+
diagnostics.append(Diagnostic(message: message, absoluteRange: absoluteRangeAtCursor))
+
}
+
return
+
} else {
+
do {
+
var handle = builder.open()
+
defer { handle.close() }
+
+
for discardToken in discardTokens {
+
endOffset += discardToken.utf16Length
+
}
+
}
+
+
if !inError {
+
inError = true
+
diagnostics.append(Diagnostic(message: message, absoluteRange: startOffset..<endOffset))
+
}
+
}
+
}
+
+
mutating func token(kind: TokenKind, metadata: TokenMetadata? = nil, error: String? = nil) {
+
var anchors = recoveryStack.current
+
if ate(kind: kind, metadata: metadata) { return }
+
anchors.insert(kind)
+
recoverUntil(anchors, expected: [kind], error: error)
+
let _ = ate(kind: kind, metadata: metadata)
+
}
+
+
mutating func token<Result>(choices: [TokenKind: (TokenMetadata?, Result)]) -> Result? {
+
var anchors = recoveryStack.current
+
+
var result: Result? = nil
+
for choice in choices {
+
if ate(kind: choice.key, metadata: choice.value.0) {
+
result = choice.value.1
+
break
+
}
+
}
+
+
if let result { return result }
+
+
for kind in choices.keys {
+
anchors.insert(kind)
+
}
+
+
recoverUntil(anchors, expected: Array(choices.keys))
+
for choice in choices {
+
if ate(kind: choice.key, metadata: choice.value.0) {
+
result = choice.value.1
+
break
+
}
+
}
+
+
return result
+
}
+
+
mutating func tryToken(kind: TokenKind, metadata: TokenMetadata? = nil) -> ControlFlow<Void> {
+
guard eat(kind: kind, metadata: metadata) else { return .abort }
+
return .commit
+
}
+
+
func structure(kind: SyntaxTreeKind? = nil, metadata: SyntaxTreeMetadata? = nil) -> SyntaxTreeBuilder.SubtreeHandle {
+
return builder.open(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() {}
+
}
+
}
+287
Sources/PterodactylSyntax/Parser.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
+
extension Parser {
+
/// This will consume at least one ``TokenKind.blockSep``, possibly also consuming some block items containing only trivia.
+
mutating func tryBlockSep() -> ControlFlow<Void> {
+
var status: ControlFlow<Void> = .abort
+
while case .commit = tryToken(kind: .blockSep) {
+
status = .commit
+
eatTrivia()
+
}
+
return status
+
}
+
+
/// This will consume ``TokenKind.blockBegin`` followed by any number of trivial block items.
+
mutating func tryBeginBlock() -> ControlFlow<Void> {
+
guard isAt(kind: .blockBegin) else { return .abort }
+
token(kind: .blockBegin)
+
eatTrivia()
+
_ = tryBlockSep()
+
return .commit
+
}
+
+
mutating func endBlock() {
+
token(kind: .blockEnd)
+
}
+
+
mutating func declLhs() {
+
var handle = structure(kind: Declaration.Lhs.kind)
+
defer { handle.close() }
+
token(kind: .identifier, metadata: TokenMetadata(semanticTokenType: .function))
+
}
+
+
struct Scanner {
+
let tokens: [Token]
+
var position: Int
+
+
var currentToken: Token { tokens[position] }
+
+
mutating func eat(kindSatisfying predicate: (TokenKind) -> Bool) -> Bool {
+
guard position < tokens.count, predicate(currentToken.kind) else { return false }
+
position += 1
+
return true
+
}
+
+
mutating func eat(kind: TokenKind) -> Bool {
+
eat(kindSatisfying: { $0 == kind })
+
}
+
+
mutating func eatTrivia() {
+
while position < tokens.count, currentToken.kind.isTrivia { position += 1 }
+
}
+
}
+
+
+
var isAtTypedBinder: Bool {
+
var scanner = Scanner(tokens: tokens, position: self.position)
+
guard scanner.eat(kind: .punctuation(.lparen)) else { return false }
+
scanner.eatTrivia()
+
guard scanner.eat(kind: .identifier) else { return false }
+
scanner.eatTrivia()
+
guard scanner.eat(kind: .punctuation(.colon)) else { return false }
+
return true
+
}
+
+
mutating func typedBinder() {
+
var handle = structure(kind: TypedBinder.kind)
+
defer { handle.close() }
+
+
token(kind: .punctuation(.lparen))
+
eatTrivia()
+
token(kind: .identifier)
+
eatTrivia()
+
token(kind: .punctuation(.colon))
+
eatTrivia()
+
expression()
+
eatTrivia()
+
token(kind: .punctuation(.rparen))
+
}
+
+
mutating func dependentFunctionType() -> ControlFlow<Void> {
+
guard isAtTypedBinder else { return .abort }
+
+
var handle = structure(kind: DependentFunctionType.kind)
+
defer { handle.close() }
+
+
while isAtTypedBinder {
+
typedBinder()
+
eatTrivia()
+
}
+
+
token(kind: .punctuation(.rightArrow), metadata: TokenMetadata(semanticTokenType: .keyword))
+
eatTrivia()
+
expression()
+
+
return .commit
+
}
+
+
mutating func identifier() -> ControlFlow<Void> {
+
guard isAt(kind: .identifier) else { return .abort }
+
token(kind: .identifier)
+
return .commit
+
}
+
+
mutating func parenthesisedExpression() -> ControlFlow<Void> {
+
guard isAt(kind: .punctuation(.lparen)) else { return .abort }
+
token(kind: .punctuation(.lparen))
+
eatTrivia()
+
expression()
+
eatTrivia()
+
token(kind: .punctuation(.rparen))
+
return .commit
+
}
+
+
mutating func applicationExpression() {
+
var handle = structure(kind: Application.kind)
+
defer { handle.close() }
+
+
atomicExpression()
+
eatTrivia()
+
+
var isApplication = false
+
while case .commit = tryAtomicExpression() {
+
eatTrivia()
+
isApplication = true
+
}
+
+
if !isApplication { handle.cancel() }
+
}
+
+
mutating func tryAtomicExpression() -> ControlFlow<Void> {
+
guard case .abort = identifier() else { return .commit }
+
guard case .abort = parenthesisedExpression() else { return .commit }
+
return .abort
+
}
+
+
mutating func atomicExpression() {
+
guard case .commit = tryAtomicExpression() else {
+
return advance(error: "Expected atomic expression")
+
}
+
}
+
+
mutating func expression() {
+
if case .commit = dependentFunctionType() { return }
+
+
var handle = structure()
+
defer { handle.close() }
+
+
applicationExpression()
+
+
var scanner = Scanner(tokens: tokens, position: position)
+
scanner.eatTrivia()
+
if scanner.eat(kind: .punctuation(.rightArrow)) {
+
handle.kind = NondependentFunctionType.kind
+
eatTrivia()
+
token(kind: .punctuation(.rightArrow), metadata: TokenMetadata(semanticTokenType: .keyword))
+
eatTrivia()
+
expression()
+
} else {
+
handle.cancel()
+
}
+
}
+
+
mutating func declRhs(declKind: SyntaxTreeKind) {
+
var handle = structure(kind: Declaration.Rhs.kind)
+
defer { handle.close() }
+
+
switch declKind {
+
case Declaration.Kinds.claim:
+
expression()
+
case Declaration.Kinds.define:
+
token(kind: .identifier, metadata: TokenMetadata(semanticTokenType: .variable)) // TODO: expr
+
case Declaration.Kinds.refine:
+
token(kind: .identifier, metadata: TokenMetadata(semanticTokenType: .keyword)) // TODO
+
default: return
+
}
+
}
+
+
mutating func tryDecl() -> ControlFlow<Void> {
+
guard isAt(kind: .identifier) else { return .abort }
+
+
var handle = structure()
+
defer { handle.close() }
+
+
declLhs()
+
+
eatTrivia()
+
+
let choices: [TokenKind: (TokenMetadata?, SyntaxTreeKind)] = [
+
.punctuation(.doubleRightArrow): Declaration.Kinds.define,
+
.punctuation(.doubleLeftArrow): Declaration.Kinds.refine,
+
.punctuation(.colon): Declaration.Kinds.claim
+
].mapValues { (TokenMetadata(semanticTokenType: .keyword), $0) }
+
+
handle.kind = token(choices: choices) ?? .error
+
+
eatTrivia()
+
+
declRhs(declKind: handle.kind)
+
+
return .commit
+
}
+
+
mutating func keyword(_ keyword: Keyword) {
+
token(kind: .keyword(keyword), metadata: TokenMetadata(semanticTokenType: .keyword))
+
}
+
+
mutating func theoryName() {
+
var handle = structure(kind: TheoryName.kind)
+
defer { handle.close() }
+
token(kind: .identifier, metadata: TokenMetadata(semanticTokenType: .interface))
+
}
+
+
mutating func theoryBlock() {
+
var handle = recoveryStack.push([.blockSep, .blockEnd])
+
defer { handle.close() }
+
+
guard case .commit = tryBeginBlock() else { return }
+
eatTrivia()
+
+
guard case .commit = tryDecl() else { return }
+
eatTrivia()
+
+
while true {
+
guard case .commit = tryBlockSep() else { break }
+
_ = tryDecl()
+
eatTrivia()
+
}
+
+
eatTrivia()
+
endBlock()
+
}
+
+
mutating func theory() -> ControlFlow<Void> {
+
guard isAt(kind: .keyword(.theory)) else { return .abort }
+
+
var handle = structure(kind: Theory.kind, metadata: SyntaxTreeMetadata(delimitedFoldingRangeKind: .region))
+
defer { handle.close() }
+
+
eatTrivia()
+
keyword(.theory)
+
eatTrivia()
+
theoryName()
+
eatTrivia()
+
keyword(.where)
+
eatTrivia()
+
theoryBlock()
+
+
return .commit
+
}
+
+
mutating func importName() {
+
var handle = structure(kind: TheoryName.kind)
+
defer { handle.close() }
+
token(kind: .identifier)
+
}
+
+
mutating func importDecl() -> ControlFlow<Void> {
+
guard isAt(kind: .keyword(.import)) else { return .abort }
+
+
var handle = structure(kind: Import.kind)
+
defer { handle.close() }
+
+
keyword(.import)
+
eatTrivia()
+
importName()
+
+
return .commit
+
}
+
+
mutating public func document() {
+
var handle = structure(kind: Document.kind)
+
defer { handle.close() }
+
+
while true {
+
guard case .commit = importDecl() else { break }
+
}
+
+
while true {
+
guard case .commit = theory() else { break }
+
}
+
+
token(kind: .eof)
+
}
+
}
+97
Sources/PterodactylSyntax/SemanticToken.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
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.lspPosition(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.character, length: length))
+
location = Position(line: location.line + 1, character: 0)
+
}
+
+
return result
+
}
+
}
+
+
extension SyntaxCursor {
+
public func collectSemanticTokens(_ sink: inout [SemanticToken], in range: Range<Int>? = nil) {
+
if let range {
+
guard range.overlaps(utf16Range) else { return }
+
}
+
+
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, in: range)
+
}
+
}
+
+
public func semanticTokens(in range: Range<Int>? = nil) -> [SemanticToken] {
+
var tokens: [SemanticToken] = []
+
collectSemanticTokens(&tokens, in: range)
+
return tokens
+
}
+
+
public var semanticTokens: [SemanticToken] {
+
semanticTokens()
+
}
+
}
+98
Sources/PterodactylSyntax/SyntaxCursor.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
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: CustomStringConvertible {
+
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
+
}
+
+
/// A sendable type capturing the initialisation parameters of a ``SyntaxCursor``.
+
public struct Options: Sendable {
+
let lineMap: LineMap
+
let node: SyntaxTree.Child
+
let utf16Offset: Int
+
public init(lineMap: LineMap, node: SyntaxTree.Child, utf16Offset: Int) {
+
self.lineMap = lineMap
+
self.node = node
+
self.utf16Offset = utf16Offset
+
}
+
+
public var cursor: SyntaxCursor {
+
SyntaxCursor(lineMap: lineMap, node: node, utf16Offset: utf16Offset)
+
}
+
}
+
+
public init(lineMap: LineMap, node: SyntaxTree.Child, utf16Offset: Int) {
+
self.lineMap = lineMap
+
self.node = node
+
self.utf16Offset = utf16Offset
+
}
+
+
private func description(indent: String) -> String {
+
var output = ""
+
switch node {
+
case .token(let token, _):
+
output += "\(indent)- \(token.kind)\n"
+
case .tree(let tree):
+
output += "\(indent)+ \(tree.kind)\n"
+
}
+
for child in children {
+
output += child.description(indent: indent + "\t")
+
}
+
return output
+
}
+
+
public var description: String {
+
description(indent: "")
+
}
+
+
public func collectHints(sink: inout [(Range<Int>, String)], in range: Range<Int>) {
+
// TODO: see if this is really working
+
guard range.overlaps(utf16Range) else { return }
+
+
if let tree = node.tree, let hint = tree.metadata?.hint {
+
sink.append((utf16Range, hint))
+
}
+
+
for child in children {
+
child.collectHints(sink: &sink, in: range)
+
}
+
}
+
}
+
+
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)
+
}
+
}
+
+36 -9
Sources/PterodactylSyntax/SyntaxTree.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
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?, children: [SyntaxTree.Child]) {
+
public init(kind: SyntaxTreeKind, metadata: SyntaxTreeMetadata? = nil, 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 {
-
var text: String {
+
public 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: []
+96
Sources/PterodactylSyntax/SyntaxTreeBuilder.swift
···
+
// SPDX-FileCopyrightText: 2025 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)
+
}
+
}
+
}
+21
Sources/PterodactylSyntax/SyntaxView.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
+
/// 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 -3
Sources/PterodactylSyntax/Token.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
-
public struct Token: Codable {
+
public struct Token: Codable, Equatable {
public let kind: TokenKind
public let text: String
public let utf16Length: Int
-
init(kind: TokenKind, text: String) {
+
public init(kind: TokenKind, text: String) {
self.kind = kind
self.text = text
self.utf16Length = text.utf16.count
+101 -7
Sources/PterodactylSyntax/Types.swift
···
//
// SPDX-License-Identifier: MPL-2.0
-
import Foundation
+
import LanguageServerProtocol
-
public enum TokenKind: Codable, Equatable, Sendable {
-
case eof
+
public enum Keyword: String, Codable, Sendable, CaseIterable {
+
case theory = "theory"
+
case `where` = "where"
+
case `import` = "import"
}
-
public enum SyntaxTreeKind: Codable, Equatable, Sendable {
-
case error
+
extension Keyword: CustomStringConvertible {
+
public var description: String { rawValue }
}
-
public struct TokenMetadata: Codable, Equatable, Sendable {
+
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 rightArrow = "->"
+
case equal = "="
+
}
+
+
extension Punctuation: CustomStringConvertible {
+
public var description: String { rawValue }
+
}
+
+
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 Keyword {
+
public var isBlockHerald: Bool {
+
switch self {
+
case .where: true
+
default: false
+
}
+
}
+
}
+
+
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(let kwd): kwd.isBlockHerald
+
default: false
+
}
+
}
+
+
}
+
+
public final class SyntaxTreeKind: Codable, Equatable, Sendable, CustomStringConvertible {
+
public static let error: SyntaxTreeKind = .init(name: "error")
+
+
public static func == (lhs: SyntaxTreeKind, rhs: SyntaxTreeKind) -> Bool {
+
lhs === rhs
+
}
+
+
let name: String
+
public var description: String { name }
+
+
required init(name: String) {
+
self.name = name
+
}
+
}
+
+
public struct TokenMetadata: Equatable, Codable, Sendable {
+
public var semanticTokenType: SemanticTokenTypes
+
public var semanticTokenModifiers: Set<SemanticTokenModifiers> = []
+
public var delimitedFoldingRangeKind: FoldingRangeKind? = nil
}
public struct SyntaxTreeMetadata: Codable, Equatable, Sendable {
-
+
public var delimitedFoldingRangeKind: FoldingRangeKind? = nil
+
public var hint: String? = 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) }
-
}
+45 -5
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 tokens = lexer.tokenize()
+
+
#expect(
+
tokens.map(\.kind) == [
+
.keyword(.theory), .whitespace, .identifier, .whitespace, .keyword(.where), .newline, .blockBegin, .whitespace, .identifier, .whitespace,
+
.punctuation(.colon),
+
.whitespace, .identifier, .newline, .newline, .blockSep, .whitespace, .identifier, .whitespace, .punctuation(.colon), .whitespace, .identifier, .blockEnd, .eof
+
])
+
}
+
+
@Test
+
func testParse() async throws {
+
let code = """
+
theory assdf where
+
asdf : (x : y asdf) (u : Vasdf) -> asaaaadf
+
asdf => asdf
+
"""
+
+
var lexer = PterodactylSyntax.Lexer(input: code)
+
let tokens = lexer.tokenize()
+
var context = Parser(source: code, tokens: tokens)
+
context.document()
+
+
let tree = context.tree
+
// let cursor = SyntaxCursor(lineMap: LineMap(source: code), node: .tree(tree), utf16Offset: 0)
+
#expect(tree.kind == Document.kind)
+
#expect(context.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.DependencyGraphOfSourceTree(sourceTreeId: treeID), ctx).get()
-
let foo = UnitName(name: "foo")
-
let bar = UnitName(name: "bar")
-
let baz = UnitName(name: "baz")
+
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")
#expect(
dependencyGraph.edges == [
···
]
)
-
let dependenciesOfBaz = try await engine.build(key: Keys.TransitiveDependencies(sourceTreeId: treeID, unitName: baz), ctx).get().dependencies
+
let dependenciesOfBaz = try await engine.build(key: Keys.SourceTree.GetDependencies(sourceTreeId: treeID, unitName: baz), ctx).get()
#expect(dependenciesOfBaz == [foo, bar])
return
}
+29
Tests/PterodactylLanguageServerTests/Test.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
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
+
+4
test.ptero
···
+
theory Group where
+
carrier : Set
+
multiply : carrier -> carrier -> carrier
+
empty : carrier