Working on parser

Changed files
+184
Sources
PterodactylSyntax
+25
Sources/PterodactylSyntax/Grammar.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
import Foundation
+
+
public protocol Grammar {
+
// static var kind: L.TreeKind { get }
+
static func before(_ parser: inout Parser) -> Bool
+
static func inside(_ parser: inout Parser) -> SyntaxTreeKind
+
}
+
+
public extension Grammar {
+
static func tryParse(_ parser: inout Parser) -> Bool {
+
guard !parser.isEndOfFile && before(&parser) else { return false }
+
parse(&parser)
+
return true
+
}
+
+
static func parse(_ parser: inout Parser) {
+
let mark = parser.open()
+
let kind = inside(&parser)
+
parser.close(mark: mark, kind: kind)
+
}
+
}
+159
Sources/PterodactylSyntax/Parser.swift
···
+
// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
+
//
+
// SPDX-License-Identifier: MPL-2.0
+
+
public struct Parser {
+
enum Event: Equatable {
+
case open(kind: SyntaxTreeKind)
+
case close
+
case advance
+
}
+
+
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] = []
+
+
private var fuel: Int = 0
+
private var position: Int = 0
+
private var events: [Event] = []
+
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 open() -> MarkOpened {
+
let mark = MarkOpened(index: events.count)
+
events.append(.open(kind: .error))
+
return mark
+
}
+
+
public mutating func close(mark: MarkOpened, kind: SyntaxTreeKind) {
+
events[mark.index] = .open(kind: kind)
+
events.append(.close)
+
}
+
+
public mutating func advance() {
+
precondition(!isEndOfFile)
+
events.append(.advance)
+
absoluteUtf16Offset += currentToken.utf16Length
+
position += 1
+
fuel = 256
+
}
+
+
public mutating func advance(error: String?) {
+
let mark = open()
+
if let error {
+
let diagnostic = Diagnostic(
+
message: error,
+
absoluteRange: absoluteRangeOfCurrentToken
+
)
+
+
diagnostics.append(diagnostic)
+
}
+
advance()
+
close(mark: mark, kind: .error)
+
}
+
+
public mutating func lookahead(_ k: Int) -> TokenKind? {
+
precondition(fuel > 0, "Parser is stuck!")
+
fuel -= 1
+
let index = position + k
+
guard tokens.indices.contains(index) else { return nil }
+
return tokens[index].kind
+
}
+
+
public mutating func eat(kindSatisfying predicate: (TokenKind) -> Bool) -> Bool {
+
guard !isEndOfFile && isAt(kindSatisfying: predicate) else { return false }
+
advance()
+
return true
+
}
+
+
public mutating func eat(kind: TokenKind) -> Bool {
+
eat { $0 == kind }
+
}
+
+
public mutating func expect(kind: TokenKind, error: String? = nil) {
+
if eat(kind: kind) { return }
+
let diagnostic = Diagnostic(
+
message: error ?? "Expected \(kind) but got \(currentToken.kind): `\(currentToken.text)`",
+
absoluteRange: absoluteRangeAtCursor
+
)
+
diagnostics.append(diagnostic)
+
}
+
+
public var tree: SyntaxTree {
+
var events = events
+
var stack: [SyntaxTree] = []
+
var cursor: Int = 0
+
+
precondition(events.popLast() == .close)
+
+
for event in events {
+
switch event {
+
case .open(let kind):
+
stack.append(SyntaxTree(kind: kind, children: []))
+
case .close:
+
let tree = stack.popLast()!
+
stack.modifyLast { last in
+
last.children.append(.tree(tree))
+
}
+
case .advance:
+
let token = tokens[cursor]
+
cursor += 1
+
stack.modifyLast { last in
+
last.children.append(.token(token))
+
}
+
}
+
}
+
+
assert(stack.count == 1)
+
return stack.popLast()!
+
}
+
}
+
+
extension Array {
+
fileprivate mutating func modifyLast(_ modifier: (inout Element) -> Void) {
+
if var last = popLast() {
+
modifier(&last)
+
append(last)
+
}
+
}
+
}