XCTLRuntimeContext.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. //
  2. // XCTLRuntimeContext.swift
  3. // notebook
  4. //
  5. // Created by 邢铖 on 2023/5/18.
  6. //
  7. import Foundation
  8. import UIKit
  9. internal class XCTLRuntimeContext: XCTLRuntimeAbstractContext {
  10. public let nativeObjectInstance: NSObject
  11. public var nativeObjectMutations = [(String, XCTLRuntimeVariable)]()
  12. private let generators: [String : XCTLGenerateProtocol.Type]
  13. private var values = [String : XCTLRuntimeVariable]()
  14. internal private(set) var lazyRunStatements = [XCTLReferencedVariableStatement]()
  15. internal let stdout = XCTLStream(onAppendBlock: {
  16. print($0, terminator: "")
  17. })
  18. internal init(nativeObjectInstance: NSObject,
  19. paragraphMembers: [String : XCTLStatement],
  20. generators: [String : XCTLGenerateProtocol.Type]) {
  21. self.nativeObjectInstance = nativeObjectInstance
  22. self.generators = generators
  23. self.setValue(XCTLRuntimeVariable(funcImpl: {
  24. if $0.count == 1,
  25. let val = $0.first {
  26. if val.type != .typeString {
  27. return .void
  28. }
  29. if let nativeImage = UIImage(named: val.stringValue) ?? UIImage(systemName: val.stringValue) {
  30. return XCTLRuntimeVariable(rawObject: nativeImage)
  31. }
  32. }
  33. return .void
  34. }), forName: "image:")
  35. self.setValue(XCTLRuntimeVariable(funcImpl: {
  36. if $0.count == 1,
  37. let val = $0.first {
  38. if val.type == .typeString {
  39. return XCTLRuntimeVariable(type: .typeString, rawValue: NSLocalizedString(val.stringValue, comment: ""))
  40. }
  41. }
  42. return .void
  43. }), forName: "string:")
  44. self.setValue(XCTLRuntimeVariable(funcImpl: {
  45. if $0.count == 2,
  46. let s1 = $0.first,
  47. let s2 = $0.last {
  48. if s1.type != .typeString {
  49. return XCTLRuntimeVariable(type: .typeString, rawValue: NSLocalizedString(s1.stringValue, comment: ""))
  50. }
  51. if s2.type != .typeString {
  52. return XCTLRuntimeVariable(type: .typeString, rawValue: NSLocalizedString(s2.stringValue, comment: ""))
  53. }
  54. if NSLocale.preferredLanguages.first?.hasPrefix("zh") ?? false || NSLocale.preferredLanguages.first?.hasPrefix("ch") ?? false {
  55. return s2
  56. }
  57. return s1
  58. }
  59. return .void
  60. }), forNames: "stringWithEnglish:chinese:", "stringWithEn:ch:")
  61. self.setValue(XCTLRuntimeVariable(funcImpl: {
  62. if $0.count == 2,
  63. let s1 = $0.first,
  64. let s2 = $0.last {
  65. if s1.type != .typeString {
  66. return XCTLRuntimeVariable(type: .typeString, rawValue: NSLocalizedString(s1.stringValue, comment: ""))
  67. }
  68. if s2.type != .typeString {
  69. return XCTLRuntimeVariable(type: .typeString, rawValue: NSLocalizedString(s2.stringValue, comment: ""))
  70. }
  71. if NSLocale.preferredLanguages.first?.hasPrefix("zh") ?? false || NSLocale.preferredLanguages.first?.hasPrefix("ch") ?? false {
  72. return s1
  73. }
  74. return s2
  75. }
  76. return .void
  77. }), forNames: "stringWithChinese:english:", "stringWithCh:en:")
  78. self.setValue(XCTLRuntimeVariable(funcImpl: {
  79. fatalError($0.first?.stringValue ?? "fatalError from XCT")
  80. }), forName: "appFatalError:")
  81. self.setValue(XCTLRuntimeVariable(funcImpl: {
  82. for (id, it) in $0.enumerated() {
  83. self.stdout.append(text: it.toString())
  84. if id != $0.count - 1 {
  85. self.stdout.append(text: " ")
  86. }
  87. }
  88. return .void
  89. }), forNames: "log", "log:", "log:_:", "log:_:_:", "log:_:_:_:",
  90. "log:_:_:_:_:", "log:_:_:_:_:_:", "log:_:_:_:_:_:_:", "log:_:_:_:_:_:_:_:")
  91. self.setValue(XCTLRuntimeVariable(funcImpl: {
  92. for (id, it) in $0.enumerated() {
  93. self.stdout.append(text: it.toString())
  94. if id != $0.count - 1 {
  95. self.stdout.append(text: " ")
  96. }
  97. }
  98. self.stdout.append(text: "\n")
  99. return .void
  100. }), forNames: "logn", "logn:", "logn:_:", "logn:_:_:", "logn:_:_:_:",
  101. "logn:_:_:_:_:", "logn:_:_:_:_:_:", "logn:_:_:_:_:_:_:", "logn:_:_:_:_:_:_:_:")
  102. self.setValue(XCTLRuntimeVariable(funcImpl: {
  103. var dest: Double = 0
  104. for it in $0 {
  105. if it.type == .typeNumber {
  106. dest += it.doubleValue
  107. }
  108. }
  109. return XCTLRuntimeVariable(type: .typeNumber, rawValue: dest.description)
  110. }), forNames: "add", "add:", "add:_:", "add:_:_:", "add:_:_:_:",
  111. "add:_:_:_:_:", "add:_:_:_:_:_:", "add:_:_:_:_:_:_:", "add:_:_:_:_:_:_:_:")
  112. self.setValue(XCTLRuntimeVariable(funcImpl: {
  113. var list = $0
  114. list = list.filter({ $0.type == .typeNumber })
  115. if list.isEmpty { return XCTLRuntimeVariable(type: .typeNumber, rawValue: "0") }
  116. var dest: Double = list.removeFirst().doubleValue
  117. for it in list {
  118. if it.type == .typeNumber {
  119. dest -= it.doubleValue
  120. }
  121. }
  122. return XCTLRuntimeVariable(type: .typeNumber, rawValue: dest.description)
  123. }), forNames: "minus", "minus:", "minus:_:", "minus:_:_:", "minus:_:_:_:",
  124. "minus:_:_:_:_:", "minus:_:_:_:_:_:", "minus:_:_:_:_:_:_:", "minus:_:_:_:_:_:_:_:")
  125. self.setValue(XCTLRuntimeVariable(funcImpl: {
  126. var dest: Double = 1
  127. for it in $0 {
  128. if it.type == .typeNumber {
  129. dest *= it.doubleValue
  130. }
  131. }
  132. return XCTLRuntimeVariable(type: .typeNumber, rawValue: dest.description)
  133. }), forNames: "mult", "mult:", "mult:_:", "mult:_:_:", "mult:_:_:_:",
  134. "mult:_:_:_:_:", "mult:_:_:_:_:_:", "mult:_:_:_:_:_:_:", "mult:_:_:_:_:_:_:_:")
  135. self.setValue(XCTLRuntimeVariable(funcImpl: {
  136. var list = $0
  137. list = list.filter({ $0.type == .typeNumber })
  138. if list.isEmpty { return XCTLRuntimeVariable(type: .typeNumber, rawValue: "0") }
  139. var dest: Double = list.removeFirst().doubleValue
  140. for it in list {
  141. if it.type == .typeNumber {
  142. dest /= it.doubleValue
  143. }
  144. }
  145. return XCTLRuntimeVariable(type: .typeNumber, rawValue: dest.description)
  146. }), forNames: "div", "div:", "div:_:", "div:_:_:", "div:_:_:_:",
  147. "div:_:_:_:_:", "div:_:_:_:_:_:", "div:_:_:_:_:_:_:", "div:_:_:_:_:_:_:_:")
  148. self.setValue(XCTLRuntimeVariable(funcImpl: {
  149. var begin: Double = 0
  150. var length: Double = 0
  151. var step: Double = 1
  152. let args = $0.filter({ $0.type == .typeNumber }).map({ $0.doubleValue })
  153. if args.count == 1 {
  154. length = args[0]
  155. }
  156. if args.count == 2 {
  157. begin = args[0]
  158. length = args[1]
  159. }
  160. if args.count == 3 {
  161. begin = args[0]
  162. length = args[1]
  163. step = args[2]
  164. }
  165. return XCTLRuntimeVariable(rawObject: XCTLRange(begin: begin, length: length, step: step))
  166. }), forNames: "range:", "range:_:", "range:_:_:")
  167. self.setValue(XCTLRuntimeVariable(type: .typeNumber, rawValue: "\(Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "-1")"), forName: "appBundleVersion")
  168. for it in paragraphMembers {
  169. self.setValue(XCTLRuntimeVariable(funcImplStmt: it.value), forName: it.key)
  170. }
  171. }
  172. private var importNames = Set<String>()
  173. private var exportNames = Set<String>()
  174. internal func valueDefined(_ name: String) -> Bool {
  175. if self.importNames.contains(name) {
  176. return true
  177. }
  178. if self.values[name] != nil {
  179. return true
  180. }
  181. return false
  182. }
  183. internal func value(forName name: String) -> XCTLRuntimeVariable? {
  184. if name == "self" {
  185. return XCTLRuntimeVariable(rawObject: self.nativeObjectInstance)
  186. }
  187. if let value = self.values[name] {
  188. return value
  189. }
  190. if importNames.contains(name),
  191. let valueFromNative = self.nativeObjectInstance.value(forKey: name) as? NSObject {
  192. let object = XCTLRuntimeVariable(rawObject: valueFromNative)
  193. self.values[name] = object
  194. return object
  195. }
  196. if let klass: AnyObject = NSClassFromString(name),
  197. let klass = klass as? NSObject {
  198. return XCTLRuntimeVariable(rawObject: klass)
  199. }
  200. return .void
  201. }
  202. internal func setValue(_ value: XCTLRuntimeVariable, forName name: String) {
  203. self.setValue(value, forNames: name)
  204. }
  205. internal func setValue(_ value: XCTLRuntimeVariable, forNames names: String...) {
  206. for name in names {
  207. self.values[name] = value
  208. if exportNames.contains(name) {
  209. self.nativeObjectMutations.append((name, value))
  210. }
  211. }
  212. }
  213. internal func setValueToRootAssumeExport(_ value: XCTLRuntimeVariable, forName name: String) {
  214. self.values[name] = value
  215. self.nativeObjectMutations.append((name, value))
  216. }
  217. internal func setValueToRoot(_ value: XCTLRuntimeVariable, forName name: String, assumeExport export: Bool) {
  218. self.values[name] = value
  219. let doExport = export || self.exportNames.contains(name)
  220. if doExport {
  221. self.nativeObjectMutations.append((name, value))
  222. }
  223. }
  224. internal func applyNativeObjectMutations() {
  225. for it in self.nativeObjectMutations {
  226. let name = it.0
  227. let value = it.1
  228. guard let property = class_getProperty(self.nativeObjectInstance.classForCoder, name),
  229. let propertyAttributes = property_getAttributes(property),
  230. let propertyAttributes = NSString(cString: propertyAttributes, encoding: String.Encoding.utf8.rawValue) as? String else {
  231. print("[XCTLWarning] The export name \(name) does not match a valid @property in nativeInstance, did the property use static callable such as Swift? Use setValueForKey.")
  232. self.nativeObjectInstance.setValue(value.nativeValue, forKey: name)
  233. continue
  234. }
  235. do {
  236. let value = try value.nativeValue(typeId: propertyAttributes[propertyAttributes.index(after: propertyAttributes.startIndex)])
  237. self.nativeObjectInstance.setValue(value, forKey: name)
  238. } catch let err {
  239. fatalError("[XCTLEngine] \(err)")
  240. }
  241. }
  242. }
  243. internal func setValueIgnoreParent(_ value: XCTLRuntimeVariable, forName name: String) {
  244. self.setValue(value, forName: name)
  245. }
  246. internal func addImport(name: String) {
  247. self.importNames.insert(name)
  248. }
  249. internal func addExport(name: String) {
  250. self.exportNames.insert(name)
  251. }
  252. internal func hasExport(name: String) -> Bool {
  253. return self.exportNames.contains(name)
  254. }
  255. internal func allocateObject(name: String, args: [XCTLRuntimeVariable]) throws -> XCTLRuntimeVariable {
  256. if let generator = self.generators[name] {
  257. let nativeObject = try generator.initWithXCT(args.compactMap({ $0.nativeValue }))
  258. let object = XCTLRuntimeVariable(rawObject: nativeObject)
  259. return object
  260. }
  261. if let klass: AnyObject = NSClassFromString(name),
  262. let klass = klass as? NSObject {
  263. if let object = klass.perform(NSSelectorFromString("alloc")).takeRetainedValue() as? NSObject {
  264. let invocation = XCTLSwiftInvocation(target: object, selector: NSSelectorFromString("init"))
  265. let result = try invocation.invokeMemberFunc(params: [])
  266. return try XCTLRuntimeVariable.variableFromSwiftAny(result)
  267. }
  268. }
  269. throw XCTLRuntimeError.generateProtocolNotFoundedError(name: name)
  270. }
  271. internal func addLazyStatement(_ stmt: XCTLReferencedVariableStatement) {
  272. self.lazyRunStatements.append(stmt)
  273. }
  274. internal func clearLazyStatements() {
  275. self.lazyRunStatements.removeAll()
  276. }
  277. internal func makeSubContext() -> XCTLRuntimeAbstractContext {
  278. return XCTLRuntimeSubContext(parent: self)
  279. }
  280. private var conditionFrame: XCTLConditionParentStatementFrame?
  281. private var listFrame: XCTLListStatementFrame?
  282. private var forFrame: XCTLForStatementFrame?
  283. func findConditionFrame() -> XCTLConditionParentStatementFrame? {
  284. return self.conditionFrame
  285. }
  286. func findListFrame() -> XCTLListStatementFrame? {
  287. return self.listFrame
  288. }
  289. func recordListFrame(_ frame: XCTLListStatementFrame?) {
  290. self.listFrame = frame
  291. }
  292. func recordConditionFrame(_ frame: XCTLConditionParentStatementFrame?) {
  293. self.conditionFrame = frame
  294. }
  295. func findForFrame() -> XCTLForStatementFrame? {
  296. return self.forFrame
  297. }
  298. func recordForFrame(_ frame: XCTLForStatementFrame?) {
  299. self.forFrame = frame
  300. }
  301. func getParentContext() -> XCTLRuntimeAbstractContext? {
  302. return nil
  303. }
  304. var variableStack = XCTLRuntimeVariableStackFrame()
  305. }