We can generalize a string writer / line scanner if we had, say, StreamProtocol.
(I have a complaint about current TextOutputStream because its func write(_:) is not throwable.)
My humble idea is like below:
public protocol WritableStream {
mutating func write<T>(contentsOf data: T) throws where T: DataProtocol
// Other requirements?
}
public protocol ReadableStream {
mutating func read(upToCount count: Int) throws -> Data?
// Other requirements?
}
public typealias StreamProtocol = WritableStream & ReadableStream
extension WritableStream {
public mutating func write(_ string: String, using encoding: String.Encoding = .utf8) throws {
// Implementation here (able to use `write(contentsOf:)`)
}
}
public class LineScanner {
private var _buffer: Data = .init()
private var _stream: any ReadableStream
public init(reading stream: any ReadableStream) {
self._stream = stream
}
public func nextLine() throws -> String? {
// Read bytes to search CR/LF/CR+LF/EOF with `read(upToCount:)` using buffer.
}
}
Now, something controversial comes to my mind.
I guess the reason why stdin, stdout, and stderr are instances of FileHandle is just because of historical one. Such concept would fit OOP.
However, we can do protocol-oriented programming in Swift.
Can't we reconsider that hierarchy?
For example,
public class StandardInput: ReadableStream {
public static var default: StandardInput { /* singleton */ }
}
public class StandardOutput: WritableStream {
public static var default: StandardOutput { /* singleton */ }
}
public class StandardError: WritableStream {
public static var default: StandardError { /* singleton */ }
}
open class FileHandle: StreamProtocol {
// :
}
Hmm, I'm not sure what should be better.