XCTLEngine.swift 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. //
  2. // XCTLEngine.swift
  3. // notebook
  4. //
  5. // Created by 邢铖 on 2023/5/18.
  6. //
  7. import Foundation
  8. import UIKit
  9. @objcMembers
  10. public class XCTLAST: NSObject {
  11. fileprivate let rootStatement: XCTLStatement
  12. fileprivate let paragraphMembers: [String : XCTLStatement]
  13. fileprivate init(rootStatement ast: XCTLStatement, paragraphMembers: [String : XCTLStatement]) {
  14. self.rootStatement = ast
  15. self.paragraphMembers = paragraphMembers
  16. }
  17. public weak var stdoutDelegate: XCTLStreamDelegate?
  18. }
  19. @objcMembers
  20. public class XCTLEngine: NSObject {
  21. public static var shared: XCTLEngine = XCTLEngine()
  22. public weak var secureComputeDelegate: XCTLSecureComputeDelegate?
  23. private static var debugMode: Bool = false
  24. private var prototypes = [String : XCTLGenerateProtocol.Type]()
  25. private func getClassesImplementingProtocol(p: Protocol) -> [AnyClass] {
  26. let classes = objc_getClassList()
  27. var ret = [AnyClass]()
  28. for cls in classes {
  29. if class_conformsToProtocol(cls, p) {
  30. ret.append(cls)
  31. }
  32. }
  33. return ret
  34. }
  35. private func objc_getClassList() -> [AnyClass] {
  36. let expectedClassCount = ObjectiveC.objc_getClassList(nil, 0)
  37. let allClasses = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(expectedClassCount))
  38. let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(allClasses)
  39. let actualClassCount:Int32 = ObjectiveC.objc_getClassList(autoreleasingAllClasses, expectedClassCount)
  40. var classes = [AnyClass]()
  41. for i in 0 ..< actualClassCount {
  42. if let currentClass: AnyClass = allClasses[Int(i)] {
  43. classes.append(currentClass)
  44. }
  45. }
  46. allClasses.deallocate()
  47. return classes
  48. }
  49. private override init() {
  50. super.init()
  51. let classes = getClassesImplementingProtocol(p: XCTLGenerateProtocol.self)
  52. for it in classes {
  53. self.prototypes["\(it)"] = it as? any XCTLGenerateProtocol.Type
  54. if Self.debugMode {
  55. print("[XCTInitializer] \(it)")
  56. }
  57. }
  58. }
  59. public func compile(fileNameWithoutExtension xct: String) -> XCTLAST? {
  60. guard let path = Bundle.main.path(forResource: xct, ofType: ".xct") else {
  61. print("no such file \(xct)")
  62. return nil
  63. }
  64. return compile(fullFilePath: path)
  65. }
  66. public func compile(fullFilePath xct: String) -> XCTLAST? {
  67. guard FileManager.default.fileExists(atPath: xct) else {
  68. print("no such file \(xct)")
  69. return nil
  70. }
  71. do {
  72. let code = try String(contentsOfFile: xct)
  73. return self.compile(code: code)
  74. } catch let err {
  75. print(err)
  76. return nil
  77. }
  78. }
  79. public func compile(code: String) -> XCTLAST? {
  80. do {
  81. let lexer = XCTLLexer(document: code)
  82. lexer.debugMode = Self.debugMode
  83. let rootStatement = XCTLRootStatement()
  84. try rootStatement.parseStatement(fromLexerToSelf: lexer, fromParent: nil)
  85. return XCTLAST(rootStatement: rootStatement, paragraphMembers: lexer.paragraphTable)
  86. } catch let err {
  87. print(err)
  88. return nil
  89. }
  90. }
  91. public func evaluate(ast: XCTLAST, sourceObject: NSObject) throws {
  92. let rootStatement = ast.rootStatement
  93. let context = XCTLRuntimeContext(nativeObjectInstance: sourceObject,
  94. paragraphMembers: ast.paragraphMembers,
  95. generators: prototypes)
  96. context.stdout.delegate = ast.stdoutDelegate
  97. _ = try rootStatement.evaluate(inContext: context)
  98. while !context.lazyRunStatements.isEmpty {
  99. let lazyRunStmts = context.lazyRunStatements
  100. context.clearLazyStatements()
  101. for it in lazyRunStmts {
  102. let stmt = it.statement
  103. if let stmt = stmt as? XCTLVariableRequiredLazyStatement {
  104. _ = try stmt.evaluate(inContext: context, withVariableReferenced: it.objectReferenced)
  105. } else {
  106. _ = try stmt.evaluate(inContext: context)
  107. }
  108. }
  109. }
  110. context.applyNativeObjectMutations()
  111. }
  112. public func enableAutoEvaluateForViewController() {
  113. let originalViewDidLoad = #selector(UIViewController.viewDidLoad)
  114. let swizzledViewDidLoad = #selector(UIViewController.swizzledXCTLEngineViewDidLoad)
  115. guard let originalMethod = class_getInstanceMethod(UIViewController.self, originalViewDidLoad),
  116. let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzledViewDidLoad) else {
  117. fatalError("Can not inject XCTLEngine to UIViewController")
  118. }
  119. let originalImp = method_getImplementation(originalMethod)
  120. let originalTypeEncoding = method_getTypeEncoding(originalMethod)
  121. class_addMethod(UIViewController.self,
  122. NSSelectorFromString("UIViewController.originalViewDidLoad"),
  123. originalImp,
  124. originalTypeEncoding)
  125. method_exchangeImplementations(originalMethod, swizzledMethod)
  126. }
  127. }
  128. public extension UIViewController {
  129. private func execute(xctFile: String, withContent content: String) {
  130. let time = Date()
  131. guard let ast = XCTLEngine.shared.compile(code: content) else {
  132. fatalError("Unable to compile XCT file \(xctFile)")
  133. }
  134. do {
  135. try XCTLEngine.shared.evaluate(ast: ast, sourceObject: self)
  136. } catch let error {
  137. fatalError("Runtime error when execute XCT file \(xctFile): \(error)")
  138. }
  139. print("[XCTLEngine-ScanClass] Finish execute \(xctFile) in \(time.distance(to: Date())) seconds")
  140. }
  141. @objc func swizzledXCTLEngineViewDidLoad() {
  142. let className = "\(self.classForCoder)"
  143. if let xctCode = XCTLEngine.shared.secureComputeDelegate?.secureCompute(XCTLEngine.shared,
  144. requireCodeFileForName: "\(className).xct") {
  145. print("[XCTLEngine-ScanClass] Execute secure network file \(className).xct")
  146. self.execute(xctFile: "\(className).xct", withContent: xctCode)
  147. }
  148. if let xctFile = Bundle.main.path(forResource: className, ofType: "xct"),
  149. let xctCode = try? String(contentsOfFile: xctFile) {
  150. print("[XCTLEngine-ScanClass] Execute disk file \(className).xct")
  151. self.execute(xctFile: "\(className).xct", withContent: xctCode)
  152. }
  153. self.perform(NSSelectorFromString("UIViewController.originalViewDidLoad"))
  154. }
  155. }