Skip to content

Instantly share code, notes, and snippets.

@BigZaphod
Last active March 9, 2021 18:26
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BigZaphod/4814485a26b62931b4bc82c5d1adf403 to your computer and use it in GitHub Desktop.
Save BigZaphod/4814485a26b62931b4bc82c5d1adf403 to your computer and use it in GitHub Desktop.
// Created by Sean Heber (@BigZaphod) on 10/12/19.
// License: BSD
import Foundation
//==---------------------------------------------------------
/// This implementation requires the ability to initialize an
/// instance of a type before it can decode it. This is to support
/// reference types automatically so that each instance is
/// only stored once in the archive. Decoding a reference
/// requires the ability to first make an instance and then
/// fill it in with the values that are read from the archive
/// in a seperate step because Swift is very strict and has
/// strong opinions about these things.
///
public protocol Archivable {
/// You'll need this. You can likely get this for free from Swift if you use structs or final classes and provide initial values for all your properties.
init()
/// Generally you don't have to provide this - use the default implementations and the property wrappers.
func write(to archive: ArchiveWriter) throws
/// Generally you don't have to provide this - use the default implementations and the property wrappers.
mutating func read(from archive: ArchiveReader) throws
}
public protocol PolymorphicArchivable: Archivable {}
public protocol ArchivablePropertyWrapper: CustomDebugStringConvertible {
associatedtype ValueType
var wrappedValue: ValueType { get set }
}
//==---------------------------------------------------------
/// The @Archive property wrapper is the magic that makes it possible to
/// "automatically" encode and decode all of your properties just by conforming
/// your type to Archivable. Mark every property you want to archive with the
/// property wrapper and the implementation should do the rest.
///
@propertyWrapper
public struct Archive<ValueType: Archivable>: ArchivablePropertyWrapper, _ArchivableProperty {
public init(wrappedValue initialValue: ValueType) {
wrapper = .init(value: initialValue)
}
public var wrappedValue: ValueType {
get { wrapper.value }
set {
if isKnownUniquelyReferenced(&wrapper) {
wrapper.value = newValue
} else {
wrapper = .init(value: newValue)
}
}
}
fileprivate func writeWrappedValue(to archive: ArchiveWriter) throws { try archive.write(wrapper.value) }
fileprivate func readWrappedValue(from archive: ArchiveReader) throws { wrapper.value = try archive.read(ValueType.self) }
private var wrapper: _ArchiveWrapper<ValueType>
}
@propertyWrapper
public struct ArchiveWeak<ValueType: Archivable & AnyObject>: ArchivablePropertyWrapper, _ArchivableProperty {
public init(wrappedValue initialValue: ValueType?) {
wrapper = .init(value: initialValue)
}
public var wrappedValue: ValueType? {
get { wrapper.value }
set {
if isKnownUniquelyReferenced(&wrapper) {
wrapper.value = newValue
} else {
wrapper = .init(value: newValue)
}
}
}
fileprivate func writeWrappedValue(to archive: ArchiveWriter) throws { try archive.write(wrapper.value) }
fileprivate func readWrappedValue(from archive: ArchiveReader) throws { wrapper.value = try archive.read(ValueType?.self) }
private var wrapper: _ArchiveWeakWrapper<ValueType>
}
extension ArchivablePropertyWrapper {
public var debugDescription: String {
return String(reflecting: wrappedValue)
}
}
extension Equatable where Self: ArchivablePropertyWrapper, ValueType: Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.wrappedValue == rhs.wrappedValue
}
}
extension Hashable where Self: ArchivablePropertyWrapper, ValueType: Hashable {
public func hash(into hasher: inout Hasher) {
wrappedValue.hash(into: &hasher)
}
}
extension Comparable where Self: ArchivablePropertyWrapper, ValueType: Comparable {
public static func < (lhs: Self, rhs: Self) -> Bool { lhs.wrappedValue < rhs.wrappedValue }
public static func <= (lhs: Self, rhs: Self) -> Bool { lhs.wrappedValue <= rhs.wrappedValue }
public static func >= (lhs: Self, rhs: Self) -> Bool { lhs.wrappedValue >= rhs.wrappedValue }
public static func > (lhs: Self, rhs: Self) -> Bool { lhs.wrappedValue > rhs.wrappedValue }
}
extension CustomStringConvertible where Self: ArchivablePropertyWrapper, ValueType: CustomStringConvertible {
public var description: String {
return wrappedValue.description
}
}
extension Archive: Equatable where ValueType: Equatable {}
extension Archive: Hashable where ValueType: Hashable {}
extension Archive: Comparable where ValueType: Comparable {}
extension Archive: CustomStringConvertible where ValueType: CustomStringConvertible {}
extension ArchiveWeak: Equatable where ValueType: Equatable {}
extension ArchiveWeak: Hashable where ValueType: Hashable {}
//==---------------------------------------------------------
// Internals
//==---------------------------------------------------------
private protocol _ArchivableProperty {
func writeWrappedValue(to archive: ArchiveWriter) throws
func readWrappedValue(from archive: ArchiveReader) throws
}
private final class _ArchiveWrapper<ValueType> {
init(value initialValue: ValueType) { value = initialValue }
var value: ValueType
}
private final class _ArchiveWeakWrapper<ValueType: AnyObject> {
init(value initialValue: ValueType?) { value = initialValue }
weak var value: ValueType?
}
extension Archivable {
// this finds all of the @Archive-able properties and returns them with their names
private func namedArchivableProperties() throws -> [String : _ArchivableProperty] {
var properties: [String : _ArchivableProperty] = [:]
var nextMirror: Mirror? = Mirror(reflecting: self)
while let mirror = nextMirror {
for (propertyName, propertyValue) in mirror.children {
if let name = propertyName, let property = propertyValue as? _ArchivableProperty {
properties[name] = property
}
}
nextMirror = mirror.superclassMirror
guard nextMirror == nil || self is PolymorphicArchivable else {
throw ArchivingError.nonPolymorphicArchivableType(type(of: self as Any))
}
}
return properties
}
public func write(to archive: ArchiveWriter) throws {
let properties = try self.namedArchivableProperties()
try archive.writeTable(properties.keys) { (key: String) in
if let property = properties[key] {
try property.writeWrappedValue(to: archive)
}
}
}
public mutating func read(from archive: ArchiveReader) throws {
let properties = try self.namedArchivableProperties()
try archive.readTable { (key: String) in
if let property = properties[key] {
try property.readWrappedValue(from: archive)
}
}
}
}
//==---------------------------------------------------------
// numerics that can read/write their raw bytes directly:
//==---------------------------------------------------------
extension Int8: Archivable {}
extension UInt8: Archivable {}
extension Int16: Archivable {}
extension UInt16: Archivable {}
extension Int32: Archivable {}
extension UInt32: Archivable {}
extension Int64: Archivable {}
extension UInt64: Archivable {}
extension Float32: Archivable {}
extension Float64: Archivable {}
// fixed width integers will always use bigEndian
extension Archivable where Self: FixedWidthInteger {
public func write(to archive: ArchiveWriter) throws {
archive.writeUnsafeBytes(of: bigEndian)
}
public mutating func read(from archive: ArchiveReader) throws {
try archive.readUnsafeBytes(into: &self)
self = bigEndian
}
}
// all other numeric types just read/write their raw bytes in whatever order
extension Archivable where Self: Numeric {
public func write(to archive: ArchiveWriter) throws {
archive.writeUnsafeBytes(of: self)
}
public mutating func read(from archive: ArchiveReader) throws {
try archive.readUnsafeBytes(into: &self)
}
}
//==---------------------------------------------------------
// Types that are encoded in terms of other archivable types:
//==---------------------------------------------------------
// encoded as a UInt8
extension Bool: Archivable {
public mutating func read(from archive: ArchiveReader) throws { try self = (archive.read(UInt8.self) > 0) }
public func write(to archive: ArchiveWriter) throws { try archive.write(UInt8(self ? 1 : 0)) }
}
// Int is always stored as an Int64
extension Int: Archivable {
public mutating func read(from archive: ArchiveReader) throws { try self = Int(archive.read(Int64.self)) }
public func write(to archive: ArchiveWriter) throws { try archive.write(Int64(self)) }
}
// UInt is always stored as a UInt64
extension UInt: Archivable {
public mutating func read(from archive: ArchiveReader) throws { try self = UInt(archive.read(UInt64.self)) }
public func write(to archive: ArchiveWriter) throws { try archive.write(UInt64(self)) }
}
// string is special because of built-in support for
// uniquing them so they are only stored once. therefore
// encoding/decoding is done in ArchiveWriter/ArchiveReader
extension String: Archivable {
public mutating func read(from archive: ArchiveReader) throws { try self = archive.readString() }
public func write(to archive: ArchiveWriter) throws { try archive.writeString(self) }
}
extension Collection where Self: Archivable {
public func write(to archive: ArchiveWriter) throws {
try archive.write(count)
try forEach(archive.write)
}
}
extension RangeReplaceableCollection where Self: Archivable {
public mutating func read(from archive: ArchiveReader) throws {
let count = try archive.read(Int.self)
removeAll(keepingCapacity: true)
reserveCapacity(count)
for _ in 0..<count { try append(archive.read(Element.self)) }
}
}
// read and write implementations are inherited from RangeReplaceableCollection and Collection
extension Array: Archivable {}
extension Data: Archivable {}
// Data slice is special and has built-in support for reading
// so it can reference the input data without making a copy.
// write implementation is inherited from Collection
extension Slice: Archivable where Base == Data {
public mutating func read(from archive: ArchiveReader) throws {
self = try archive.readDataSlice()
}
}
// write implementation is inherited from Collection
extension Set: Archivable where Element: Archivable {
public mutating func read(from archive: ArchiveReader) throws {
let count = try archive.read(Int.self)
removeAll(keepingCapacity: true)
reserveCapacity(count)
for _ in 0..<count {
try insert(archive.read(Element.self))
}
}
}
extension Dictionary: Archivable where Key: Archivable {
public func write(to archive: ArchiveWriter) throws {
try archive.write(count)
for (key, value) in self {
try archive.write(key)
try archive.write(value)
}
}
public mutating func read(from archive: ArchiveReader) throws {
let count = try archive.read(Int.self)
removeAll(keepingCapacity: true)
reserveCapacity(count)
for _ in 0..<count {
let key = try archive.read(Key.self)
let value = try archive.read(Value.self)
self[key] = value
}
}
}
private protocol ArchivableOptional: Archivable {}
extension Optional: ArchivableOptional {
public init() {
self = .none
}
public func write(to archive: ArchiveWriter) throws {
if let value = self {
try archive.write(true)
try archive.write(value)
} else {
try archive.write(false)
}
}
public mutating func read(from archive: ArchiveReader) throws {
if try archive.read(Bool.self) {
try self = .some(archive.read(Wrapped.self))
} else {
self = .none
}
}
}
// enums, OptionSets, etc can be archived automatically
// if they are represented by a raw value that is archivable
extension Archivable where Self: RawRepresentable, RawValue: Archivable {
public func write(to archive: ArchiveWriter) throws { try archive.write(rawValue) }
public mutating func read(from archive: ArchiveReader) throws {
guard let value = Self.init(rawValue: try archive.read(RawValue.self)) else {
throw ArchivingError.readFailed
}
self = value
}
}
/// Errors that may be thrown by the archiving system.
public enum ArchivingError: Error {
case incompatibleArchiver
case writeFailed
case readFailed
case notArchivable(Any.Type)
case cannotWriteUnregisteredPolymorphicArchivableType(Any.Type)
case cannotReadUnregisteredPolymorphicArchivableType(String) // your base class or root protocol may need to be PolymorphicArchivable or you forgot to call ArchivablePolymorphicType.register()
case cannotReadType(Any.Type)
case nonPolymorphicArchivableType(Any.Type)
}
/// This takes an object/value and converts it and everything it references into an archive.
public final class ArchiveWriter {
public static let encodingVersion: Int = 2
public var userInfo: Any?
public static func data<T: Archivable>(for value: T, as type: T.Type, userInfo: Any? = nil) throws -> Data {
try ArchiveWriter(userInfo).encodeRoot(value)
}
private init(_ userInfo: Any?) {
self.userInfo = userInfo
}
private var buffers: [Data] = []
private var encodedReferenceIDs: [ObjectIdentifier : Int] = [:]
private var encodedStringIDs: [String : Int] = [:]
private var encodedValues: [Data] = []
private func encodeRoot<T: Archivable>(_ value: T) throws -> Data {
try encodeBox {
// first write the simplest header ever
try write(ArchiveWriter.encodingVersion as Int)
// box up the value - this process will find and encode all of the strings and other objects it depends on
let boxedValue = try encodeBox({ try write(value) })
// write the objects and strings that were generated while boxing
try write(encodedValues)
// finally write the value's box
try write(boxedValue)
}
}
private func encodeBox(_ block: () throws -> Void) throws -> Data {
buffers.append(Data(capacity: 128))
try block()
return buffers.removeLast()
}
private func writeReference<T>(_ obj: T) throws {
let ref = ObjectIdentifier(obj as AnyObject)
if let idx = encodedReferenceIDs[ref] {
try write(idx)
} else {
let idx = encodedValues.count
try write(idx)
encodedReferenceIDs[ref] = idx
// this is very important!
// while boxing the value, we may end up back inside this function and the count needs
// to be correct or else the index numbers will be off. this ensures there's a slot in
// the array at the right place once the boxing is finished.
encodedValues.append(Data())
encodedValues[idx] = try encodeBox {
try writeValue(obj)
}
}
}
private func writeValue<T>(_ value: T) throws {
if let optionalValue = value as? ArchivableOptional {
try optionalValue.write(to: self)
} else if let polymorphicArchivableValue = value as? PolymorphicArchivable {
let valueType = type(of: polymorphicArchivableValue)
guard let typeName = ArchivablePolymorphicType.registeredName(for: valueType) else {
throw ArchivingError.cannotWriteUnregisteredPolymorphicArchivableType(valueType)
}
try writeString(typeName)
try polymorphicArchivableValue.write(to: self)
} else if let archivableValue = value as? Archivable {
try archivableValue.write(to: self)
} else {
throw ArchivingError.notArchivable(type(of: value))
}
}
fileprivate func writeString(_ string: String) throws {
if let idx = encodedStringIDs[string] {
try write(idx)
} else {
let idx = encodedValues.count
try write(idx)
encodedStringIDs[string] = idx
encodedValues.append(Data(string.utf8))
}
}
public func writeUnsafeBytes<T>(of value: T) {
withUnsafeBytes(of: value) {
buffers[buffers.count - 1].append($0.bindMemory(to: UInt8.self).baseAddress!, count: MemoryLayout<T>.size)
}
}
public func write<T>(_ value: T) throws {
if type(of: value) is AnyClass {
try writeReference(value)
} else {
try writeValue(value)
}
}
public func writeTable<TableKeys: Collection>(_ keys: TableKeys, valueWriter: (String) throws -> Void) throws where TableKeys.Element == String {
try write(keys.count)
for key in keys {
try write(encodeBox({
try writeString(key)
try valueWriter(key)
}))
}
}
}
/// This is the opposite of the ArchiveWriter.
public final class ArchiveReader {
public var userInfo: Any?
public static func read<T: Archivable>(_ type: T.Type, from source: Data, userInfo: Any? = nil) throws -> T {
try ArchiveReader(userInfo).decodeRoot(type, from: source)
}
private init(_ userInfo: Any?) {
self.userInfo = userInfo
}
private var slices: [Slice<Data>] = []
private var encodedValues: [Slice<Data>] = []
private var decodedValues: [Int : Archivable] = [:]
private func decodeRoot<T: Archivable>(_ type: T.Type, from data: Data) throws -> T {
try decodeBox(Slice(data)) {
// immediately read the header
let encodingVersion = try read(Int.self)
// we only support this version for now
guard encodingVersion == ArchiveWriter.encodingVersion else { throw ArchivingError.incompatibleArchiver }
// read the dependencies (strings and other objects)
encodedValues = try read([Slice<Data>].self)
// now read the root value from inside its box
return try decodeBox(read(Slice<Data>.self)) {
try read(T.self)
}
}
}
private func decodeBox<T>(_ box: Slice<Data>, block: () throws -> T) rethrows -> T {
slices.append(box)
defer { slices.removeLast() }
return try block()
}
private func readReference(of type: Any.Type) throws -> Archivable {
let idx = try read(Int.self)
if let obj = decodedValues[idx] {
return obj
}
return try decodeBox(encodedValues[idx]) {
let archivableType = try readArchivedType(for: type)
var obj = archivableType.init()
decodedValues[idx] = obj
try obj.read(from: self)
return obj
}
}
private func readArchivedType(for type: Any.Type) throws -> Archivable.Type {
// try to read an encoded name if the type is known to be PolymorphicArchivable or if the type is NOT known to be Archivable
// this catches scenerios where the type is actually a .Protocol or something because we are reading without a known concrete type.
if type is PolymorphicArchivable.Type || !(type is Archivable.Type) {
let typeName = try readString()
guard let polymorphicType = ArchivablePolymorphicType.registeredType(for: typeName) else {
throw ArchivingError.cannotReadUnregisteredPolymorphicArchivableType(typeName)
}
return polymorphicType
}
guard let archivableType = type as? Archivable.Type else {
throw ArchivingError.cannotReadType(type)
}
return archivableType
}
fileprivate func readString() throws -> String {
let idx = try read(Int.self)
if let value = decodedValues[idx] {
guard let str = value as? String else {
throw ArchivingError.readFailed
}
return str
}
guard let str = String(bytes: encodedValues[idx], encoding: .utf8) else {
throw ArchivingError.readFailed
}
decodedValues[idx] = str
return str
}
fileprivate func readDataSlice() throws -> Slice<Data> {
let count = try read(Int.self)
let slice = slices.removeLast()
defer { slices.append(slice.dropFirst(count)) }
return slice[slice.startIndex ..< slice.startIndex + count]
}
public func readUnsafeBytes<T>(into value: inout T) throws {
let count = MemoryLayout<T>.size
let slice = slices.removeLast()
guard slice.count >= count else {
throw ArchivingError.readFailed
}
withUnsafeMutableBytes(of: &value) {
slice.base.copyBytes(to: $0.bindMemory(to: UInt8.self).baseAddress!, from: slice.startIndex..<(slice.startIndex + count))
}
slices.append(slice.dropFirst(count))
}
public func read<T>(_ type: T.Type) throws -> T {
var archivableValue: Archivable
if type is AnyClass {
archivableValue = try readReference(of: type)
} else {
let archivableType = try readArchivedType(for: type)
archivableValue = archivableType.init()
try archivableValue.read(from: self)
}
guard let recastValue = archivableValue as? T else {
throw ArchivingError.readFailed
}
return recastValue
}
public func readTable(_ valueReader: (String) throws -> Void) throws {
let count = try read(Int.self)
for _ in 0..<count {
let box = try read(Slice<Data>.self)
try decodeBox(box) {
let key = try readString()
try valueReader(key)
}
}
}
}
public enum ArchivablePolymorphicType {
public static func register(_ type: PolymorphicArchivable.Type) {
let typeName = String(reflecting: type)
let identifier = ObjectIdentifier(type)
// ignore the registration if this type is already registered
if registeredName(for: type) == typeName, registeredType(for: typeName) == type {
return
}
// sanity checks
precondition(typeForName[typeName] == nil)
precondition(nameForType[identifier] == nil)
// register
typeForName[typeName] = type
nameForType[identifier] = typeName
}
fileprivate static func registeredName(for type: Any.Type) -> String? {
return nameForType[ObjectIdentifier(type)]
}
fileprivate static func registeredType(for name: String) -> PolymorphicArchivable.Type? {
return typeForName[name]
}
private static var typeForName: [String : PolymorphicArchivable.Type] = [:]
private static var nameForType: [ObjectIdentifier : String] = [:]
}
// First let's define an enum for some spaceship size classes we will need later.
// The `Archivable` protocol will automatically work for enums as long as the `RawType`
// of the enum is an `Archivable` type. In this case the enum is set as a `String` which
// is given an `Archivable` conformance by the `Archivable` implementation itself.
enum ShipSizeClass: String, Archivable {
case galaxy, excelsior, constitution
// this default initializer is unfortunately needed by `Archivable` because all `Archivable`
// types must have an empty initializer implemented. This requirement exists in order to
// support encoding graphs of objects that have circular references. Since Swift essentially
// integrates both `alloc` and `init` into a single initialization step, I could not find any
// other way to do this without a requirement along these lines.
//
// If you use structs or final classes, Swift will generate an empty initializer for you as
// long as you define defaults for all of your properties (which is super nice), but no such
// convenience exists (or is usually reasonable) for enums, so we have to do it ourselves
// and cringe a little about it. If you hide it in an extension you can pretend it doesn't
// actually exist and move on with your life..... maybe....
init() { self = .galaxy }
}
// This protocol will act as a kind of "abstract base class" for our ships. Since we want to have
// an array of `[Ship]` in our space stations, we need to make all Ship's a `PolymorphicArchivable`
// type or else the archiver will not be able to correctly encode and decode the array elements.
//
// Note that this same consideration is needed when using classes that will have subclasses - so
// be aware of this. If you want to have arrays of a base type that will have subtype instances
// in that array, for example, then those types will need to use `PolymorphicArchivable`. In my
// experiments with this so far, it's not needed too often if you keep the data structures simple.
protocol Ship: PolymorphicArchivable {
var registry: String { get set }
var sizeClass: ShipSizeClass { get set }
}
// Let's define some ship types!
// (This is a pretty silly way to do this, but it's just an example. :P)
// Since `Ship` is already `PolymorphicArchivable`, we don't need to explicitly conform each type to it.
// However each property we want to encode in our archive *does* need to be marked with the `@Archive`
// property wrapper so it knows which things to actually write to the archive. Unfortunately Swift has
// no way to require a property declared in a protocol to adopt a specific property wrapper, so there's
// some duplication here that, in theory, shouldn't be necessary - but it's not too bad. Using a base
// class instead of a base protocol arrangement would avoid that if you really wanted to, I suppose.
// Any properties that are not marked with `@Archive` will not be saved or restored by the system.
struct Intrepid: Ship {
@Archive var registry = "NCC-38907"
@Archive var sizeClass: ShipSizeClass = .excelsior
}
final class Excalibur: Ship {
@Archive var registry = "NCC-1664"
@Archive var sizeClass: ShipSizeClass = .constitution
}
struct Enterprise: Ship {
@Archive var registry = "NCC-1701-D"
@Archive var sizeClass: ShipSizeClass = .galaxy
}
// Now we need a space station for all of these ships to visit!
// This is where the `PolymorphicArchivable` really comes into play. Here we have an array of
// docked ships - but it's defined as `[Ship]`. You may notice that this array's elements are not
// a specific concrete type (like just `[Excalibur]` or `[Enterprise]` or whatever) but instead
// simply defined as containing instances of types that are known to conform to `Ship`. Each
// individual value in this array could even be of totally different underlying types. Eagle-eyed
// readers may have noticed that `Excalibur` is actually defined as a class type while all of the
// others are structs - and yet this still works! I don't think you can do this with `Codable`
// unless you use wrapper objects of your own (or perhaps use a property wrapper implementation
// that does something like that for you automatically).
final class Station: Archivable {
@Archive var docked: [Ship] = []
}
func example() {
// Any polymorphic type needs to be registered before it will archive/unarchive. This is required because
// Swift has no way (that I know of) to translate a string name of a type into a Type instance. If the runtime
// ever gets this ability, this step would no longer be necessary.
// Attempting to archive a polymorphic type that isn't registered will result throw an error so it's easy to catch.
ArchivablePolymorphicType.register(Intrepid.self)
ArchivablePolymorphicType.register(Excalibur.self)
ArchivablePolymorphicType.register(Enterprise.self)
// Let's make some stuff to archive!
let originalStarBase = Station()
originalStarBase.docked.append(Enterprise())
originalStarBase.docked.append(Excalibur())
originalStarBase.docked.append(Intrepid())
// prints: ["NCC-1701-D", "NCC-1664", "NCC-38907"]
print(originalStarBase.docked.map({ $0.registry }))
// Let's do some archiving shenanigans...
do {
// make an archive of the original starbase
let archivedData = try ArchiveWriter.data(for: originalStarBase, as: Station.self)
// unarchive the data into a new instance of the previously archived starbase
let restoredStarBase = try ArchiveReader.read(Station.self, from: archivedData)
// as one would expect, the same ships are still docked in the same order:
// ["NCC-1701-D", "NCC-1664", "NCC-38907"]
print(restoredStarBase.docked.map({ $0.registry }))
// No errors!
print("Made it so!")
} catch let err {
print(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment