123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- //
- // XCTLEngine.swift
- // notebook
- //
- // Created by 邢铖 on 2023/5/18.
- //
- import Foundation
- import UIKit
- @objcMembers
- public class XCTLAST: NSObject {
-
- fileprivate let rootStatement: XCTLStatement
-
- fileprivate let paragraphMembers: [String : XCTLStatement]
-
- fileprivate init(rootStatement ast: XCTLStatement, paragraphMembers: [String : XCTLStatement]) {
- self.rootStatement = ast
- self.paragraphMembers = paragraphMembers
- }
-
- public weak var stdoutDelegate: XCTLStreamDelegate?
-
- }
- @objcMembers
- public class XCTLEngine: NSObject {
-
- public static var shared: XCTLEngine = XCTLEngine()
-
- public weak var secureComputeDelegate: XCTLSecureComputeDelegate?
-
- private static var debugMode: Bool = false
-
- private var prototypes = [String : XCTLGenerateProtocol.Type]()
-
- private func getClassesImplementingProtocol(p: Protocol) -> [AnyClass] {
- let classes = objc_getClassList()
- var ret = [AnyClass]()
- for cls in classes {
- if class_conformsToProtocol(cls, p) {
- ret.append(cls)
- }
- }
- return ret
- }
- private func objc_getClassList() -> [AnyClass] {
- let expectedClassCount = ObjectiveC.objc_getClassList(nil, 0)
- let allClasses = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(expectedClassCount))
- let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(allClasses)
- let actualClassCount:Int32 = ObjectiveC.objc_getClassList(autoreleasingAllClasses, expectedClassCount)
- var classes = [AnyClass]()
- for i in 0 ..< actualClassCount {
- if let currentClass: AnyClass = allClasses[Int(i)] {
- classes.append(currentClass)
- }
- }
- allClasses.deallocate()
- return classes
- }
-
- private override init() {
- super.init()
- let classes = getClassesImplementingProtocol(p: XCTLGenerateProtocol.self)
- for it in classes {
- self.prototypes["\(it)"] = it as? any XCTLGenerateProtocol.Type
- if Self.debugMode {
- print("[XCTInitializer] \(it)")
- }
- }
- }
-
- public func compile(fileNameWithoutExtension xct: String) -> XCTLAST? {
- guard let path = Bundle.main.path(forResource: xct, ofType: ".xct") else {
- print("no such file \(xct)")
- return nil
- }
- return compile(fullFilePath: path)
- }
-
- public func compile(fullFilePath xct: String) -> XCTLAST? {
- guard FileManager.default.fileExists(atPath: xct) else {
- print("no such file \(xct)")
- return nil
- }
- do {
- let code = try String(contentsOfFile: xct)
- return self.compile(code: code)
- } catch let err {
- print(err)
- return nil
- }
- }
-
- public func compile(code: String) -> XCTLAST? {
- do {
- let lexer = XCTLLexer(document: code)
- lexer.debugMode = Self.debugMode
- let rootStatement = XCTLRootStatement()
- try rootStatement.parseStatement(fromLexerToSelf: lexer, fromParent: nil)
- return XCTLAST(rootStatement: rootStatement, paragraphMembers: lexer.paragraphTable)
- } catch let err {
- print(err)
- return nil
- }
- }
-
- public func evaluate(ast: XCTLAST, sourceObject: NSObject) throws {
- let rootStatement = ast.rootStatement
- let context = XCTLRuntimeContext(nativeObjectInstance: sourceObject,
- paragraphMembers: ast.paragraphMembers,
- generators: prototypes)
- context.stdout.delegate = ast.stdoutDelegate
- _ = try rootStatement.evaluate(inContext: context)
- while !context.lazyRunStatements.isEmpty {
- let lazyRunStmts = context.lazyRunStatements
- context.clearLazyStatements()
- for it in lazyRunStmts {
- let stmt = it.statement
- if let stmt = stmt as? XCTLVariableRequiredLazyStatement {
- _ = try stmt.evaluate(inContext: context, withVariableReferenced: it.objectReferenced)
- } else {
- _ = try stmt.evaluate(inContext: context)
- }
- }
- }
- context.applyNativeObjectMutations()
- }
-
- public func enableAutoEvaluateForViewController() {
- let originalViewDidLoad = #selector(UIViewController.viewDidLoad)
- let swizzledViewDidLoad = #selector(UIViewController.swizzledXCTLEngineViewDidLoad)
- guard let originalMethod = class_getInstanceMethod(UIViewController.self, originalViewDidLoad),
- let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzledViewDidLoad) else {
- fatalError("Can not inject XCTLEngine to UIViewController")
- }
- let originalImp = method_getImplementation(originalMethod)
- let originalTypeEncoding = method_getTypeEncoding(originalMethod)
- class_addMethod(UIViewController.self,
- NSSelectorFromString("UIViewController.originalViewDidLoad"),
- originalImp,
- originalTypeEncoding)
- method_exchangeImplementations(originalMethod, swizzledMethod)
- }
-
- }
- public extension UIViewController {
-
- private func execute(xctFile: String, withContent content: String) {
- let time = Date()
- guard let ast = XCTLEngine.shared.compile(code: content) else {
- fatalError("Unable to compile XCT file \(xctFile)")
- }
- do {
- try XCTLEngine.shared.evaluate(ast: ast, sourceObject: self)
- } catch let error {
- fatalError("Runtime error when execute XCT file \(xctFile): \(error)")
- }
- print("[XCTLEngine-ScanClass] Finish execute \(xctFile) in \(time.distance(to: Date())) seconds")
- }
-
- @objc func swizzledXCTLEngineViewDidLoad() {
- let className = "\(self.classForCoder)"
- if let xctCode = XCTLEngine.shared.secureComputeDelegate?.secureCompute(XCTLEngine.shared,
- requireCodeFileForName: "\(className).xct") {
- print("[XCTLEngine-ScanClass] Execute secure network file \(className).xct")
- self.execute(xctFile: "\(className).xct", withContent: xctCode)
- }
- if let xctFile = Bundle.main.path(forResource: className, ofType: "xct"),
- let xctCode = try? String(contentsOfFile: xctFile) {
- print("[XCTLEngine-ScanClass] Execute disk file \(className).xct")
- self.execute(xctFile: "\(className).xct", withContent: xctCode)
- }
- self.perform(NSSelectorFromString("UIViewController.originalViewDidLoad"))
- }
-
- }
|