BytecodeWriter.swift 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. // Sources/SwiftProtobufPluginLibrary/BytecodeCompiler.swift - Internal bytecode compiler
  2. //
  3. // Copyright (c) 2014 - 2025 Apple Inc. and the project authors
  4. // Licensed under Apache License v2.0 with Runtime Library Exception
  5. //
  6. // See LICENSE.txt for license information:
  7. // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
  8. //
  9. // -----------------------------------------------------------------------------
  10. import Foundation
  11. import SwiftProtobuf
  12. /// Writes bytecode into a string that is suitable for use as a Swift string literal (i.e., for
  13. /// appending to generated code).
  14. ///
  15. /// See `SwiftProtobuf.BytecodeInterpreter` for more information on the format used here.
  16. package struct BytecodeWriter<Instruction: RawRepresentable> where Instruction.RawValue == UInt64 {
  17. /// The Swift string literal that represents the written bytecode, including the delimiting
  18. /// quotes.
  19. package var stringLiteral: String { #""\#(code)""# }
  20. /// The contents of the Swift string literal representing the written bytecode, without the
  21. /// delimiting quotes.
  22. private var code: String = "" {
  23. didSet {
  24. hasData = true
  25. }
  26. }
  27. /// Indicates whether any data other than the program format identifier has been written to the
  28. /// bytecode stream.
  29. package var hasData: Bool = false
  30. /// Creates a new bytecode writer, writing the program format as the first value in the stream.
  31. package init() {
  32. writeUInt64(latestBytecodeProgramFormat)
  33. // Clear this back out, because we only want to track writes that come after the program
  34. // format.
  35. self.hasData = false
  36. }
  37. /// Writes the integer opcode corresponding to the given instruction to the bytecode stream.
  38. package mutating func writeOpcode(of instruction: Instruction) {
  39. writeUInt64(instruction.rawValue)
  40. }
  41. /// Writes a signed 32-bit integer to the bytecode stream.
  42. ///
  43. /// This is provided as its own primitive operation because 32-bit values are extremely common
  44. /// as field numbers (0 to 2^29-1) and enum cases (-2^31 to 2^31-1). In particular for enum
  45. /// cases, using this function specifically for those cases avoids making mistakes involving
  46. /// sign- vs. zero-extension between differently-sized integers.
  47. package mutating func writeInt32(_ value: Int32) {
  48. // `Int32`s are stored by converting them bit-wise to a `UInt32` and then zero-extended to
  49. // `UInt64`, since this representation is smaller than sign-extending them to 64 bits.
  50. writeUInt64(UInt64(UInt32(bitPattern: value)))
  51. }
  52. /// Writes an unsigned 64-bit integer to the bytecode stream.
  53. package mutating func writeUInt64(_ value: UInt64) {
  54. func append(_ value: UInt64) {
  55. // Print the normal scalar if it's ASCII-printable so that we only use longer `\u{...}`
  56. // sequences for those that are not.
  57. if value == 0 {
  58. code.append("\\0")
  59. } else if isprint(Int32(truncatingIfNeeded: value)) != 0 {
  60. self.append(escapingIfNecessary: UnicodeScalar(UInt32(truncatingIfNeeded: value))!)
  61. } else {
  62. code.append(String(format: "\\u{%x}", value))
  63. }
  64. }
  65. var v = value
  66. while v > 0x3f {
  67. append(v & 0x3f | 0x40)
  68. v &>>= 6
  69. }
  70. append(v)
  71. }
  72. /// Writes the given string literal into the bytecode stream with null termination.
  73. ///
  74. /// - Precondition: `string` must not have any embedded zero code points.
  75. package mutating func writeNullTerminatedString(_ string: String) {
  76. for scalar in string.unicodeScalars {
  77. append(escapingIfNecessary: scalar)
  78. }
  79. writeUInt64(0)
  80. }
  81. /// Writes the given collection of null-terminated strings to the bytecode stream.
  82. ///
  83. /// The format of a string collection is to write the number of strings first as an integer,
  84. /// then write that many null-terminated strings without delimiters.
  85. package mutating func writeNullTerminatedStringArray(_ strings: some Collection<String>) {
  86. writeUInt64(UInt64(strings.count))
  87. for string in strings {
  88. writeNullTerminatedString(string)
  89. }
  90. }
  91. /// Appends the given Unicode scalar to the bytecode literal, escaping it if necessary for use
  92. /// in Swift code.
  93. private mutating func append(escapingIfNecessary scalar: Unicode.Scalar) {
  94. switch scalar {
  95. case "\\", "\"":
  96. code.unicodeScalars.append("\\")
  97. code.unicodeScalars.append(scalar)
  98. default:
  99. code.unicodeScalars.append(scalar)
  100. }
  101. }
  102. }