// p4rse, Copyright 2026, Will Hawkins // // This file is part of p4rse. // // This file is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . import Foundation import System /// Represent a location in a post-preprocessed piece of P4 source code. public struct SourceLocation: Equatable, CustomStringConvertible { public let range: Range public init(_ start: Int, _ extent: Int) { self.range = start..<(start + extent) } public init(_ range: Range) { self.range = range } public func contains(_ other: SourceLocation) -> Bool { return self.range.contains(other.range) } public var description: String { return "{\(self.range.lowerBound), \(self.self.range.upperBound - self.range.lowerBound)}" } } /// Represent a set of directories containing P4 code that can be accessed with relative paths. public struct SourceManager { let paths: [FilePath] public init(_ paths: [FilePath]) { self.paths = paths } public func firstExisting(_ file: FilePath) -> FilePath? { let fm = FileManager() for path in self.paths { let combined = path.pushing(file) if fm.fileExists(atPath: combined.string) { return combined } } return .none } } /// Represent preprocessed P4 code and retain information about source filenames. public struct FileSourceLocation: Equatable, CustomStringConvertible { let location: SourceLocation let path: FilePath let nested: [FileSourceLocation] public init( _ location: SourceLocation, _ path: FilePath, _ nested: [FileSourceLocation] = Array() ) { self.location = location self.path = path self.nested = nested } public func getLocation() -> SourceLocation { return self.location } public func getPath() -> FilePath { return self.path } public func getNestedLocations() -> [FileSourceLocation] { return self.nested } public var description: String { return "\(self.path): \(self.location) (Nested: " + self.nested.map({ location in return "\(location)" }).joined(separator: ",") + ")" } } /// Represent preprocessed P4 code. public struct SourceCode { let code: String let manager: SourceManager let locations: FileSourceLocation public init(_ contents: String, _ manager: SourceManager, _ locations: FileSourceLocation) { self.code = contents self.manager = manager self.locations = locations } public func getSource() -> String { return self.code } public func getManager() -> SourceManager { return self.manager } public func getLocations() -> FileSourceLocation { return self.locations } } func do_preprocess( _ file: URL, _ manager: SourceManager, _ starting: Int = 0 ) -> Result<(FileSourceLocation, String)> { // First (1) match group has the name of the include file. let re = /\#include[\s]*<([^\s>]*)>/ do { var locations: [FileSourceLocation] = Array() var contents = try String.init(contentsOf: file, encoding: String.defaultCStringEncoding) var changed = true while changed { changed = false for match in contents.matches(of: re) { let before = contents[.. Result { // First, decide whether the given file exists at the path the user gave. let fm = FileManager() let file_to_open = if !fm.fileExists(atPath: file.string) { self.manager.firstExisting(file) } else { file } guard let file_to_open else { return .Error(Error(withMessage: "Could not open \(file) for preprocessing")) } guard let url = URL.init(filePath: file_to_open) else { return .Error(Error(withMessage: "Could not convert \(file_to_open) into a URL")) } switch do_preprocess(url, self.manager) { case .Ok((let location, let contents)): return .Ok(SourceCode(contents, self.manager, location)) case .Error(let e): return .Error(e) } } }