Axxord.st 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. Smalltalk createPackage: 'Axxord'!
  2. Object subclass: #Axes
  3. instanceVariableNames: ''
  4. package: 'Axxord'!
  5. !Axes class methodsFor: 'parsing'!
  6. parse: message
  7. | result stack anArray |
  8. anArray := message tokenize: ' '.
  9. result := #().
  10. stack := { result }.
  11. anArray do: [ :each |
  12. | asNum inner close |
  13. close := 0.
  14. inner := each.
  15. [ inner notEmpty and: [ inner first = '(' ]] whileTrue: [ inner := inner allButFirst. stack add: (stack last add: #()) ].
  16. [ inner notEmpty and: [ inner last = ')' ]] whileTrue: [ inner := inner allButLast. close := close + 1 ].
  17. (inner notEmpty and: [ inner first = '~' ]) ifTrue: [ inner := { inner allButFirst } ].
  18. asNum := inner isString ifTrue: [ (inner ifEmpty: [ 'NaN' ]) asNumber ] ifFalse: [ inner ].
  19. asNum = asNum ifTrue: [ stack last add: asNum ] ifFalse: [
  20. inner ifNotEmpty: [ stack last add: inner ] ].
  21. close timesRepeat: [ stack removeLast ] ].
  22. ^ result
  23. ! !
  24. Object subclass: #Axolator
  25. instanceVariableNames: 'root'
  26. package: 'Axxord'!
  27. !Axolator methodsFor: 'accessing'!
  28. root
  29. ^root
  30. !
  31. root: anObject
  32. root := anObject
  33. ! !
  34. !Axolator methodsFor: 'action'!
  35. model: anEavModel modify: aBlock
  36. | newValue |
  37. newValue := aBlock value: (anEavModel on: self).
  38. anEavModel on: self put: newValue deepCopy
  39. !
  40. model: anEavModel read: aBlock
  41. aBlock value: (anEavModel on: self) deepCopy
  42. ! !
  43. !Axolator class methodsFor: 'instance creation'!
  44. on: anObject
  45. ^self new root: anObject
  46. ! !
  47. Object subclass: #Axon
  48. instanceVariableNames: 'factory'
  49. package: 'Axxord'!
  50. !Axon commentStamp!
  51. I represent a pub-sub based on a key (called 'aspect').
  52. I manage aspect-block subscriptions (called 'interests') as well as run blocks of dirtied interests.
  53. The interest objects are responsible of decision if the change of an aspect is relevant for them.
  54. Interest object must be subclasses of `AxonInterest`.
  55. My subclasses must provide implementation for:
  56. - add:
  57. - do:
  58. - clean!
  59. !Axon methodsFor: 'action'!
  60. addInterest: anInterest
  61. self
  62. add: (anInterest flag; yourself);
  63. dirty: true
  64. !
  65. changed: anAspect
  66. | needsToRun |
  67. needsToRun := false.
  68. self do: [ :each |
  69. (each accepts: anAspect) ifTrue: [
  70. each flag.
  71. needsToRun := true ]].
  72. self dirty: needsToRun
  73. !
  74. changedAll
  75. | needsToRun |
  76. needsToRun := false.
  77. self do: [ :each |
  78. each flag.
  79. needsToRun := true ].
  80. self dirty: needsToRun
  81. !
  82. dirty: aBoolean
  83. aBoolean ifTrue: [[ self run ] fork]
  84. !
  85. run
  86. [
  87. | needsClean |
  88. needsClean := false.
  89. self do: [ :each |
  90. each isFlagged ifTrue: [ each run ].
  91. each isEnabled ifFalse: [ needsClean := true ]
  92. ].
  93. needsClean ifTrue: [ self clean ]
  94. ] on: Error do: [ self dirty: true ]
  95. ! !
  96. !Axon methodsFor: 'injecting'!
  97. registerIn: anObject
  98. <inlineJS: 'anObject.$axon$=self'>
  99. ! !
  100. Axon subclass: #DumbAxon
  101. instanceVariableNames: ''
  102. package: 'Axxord'!
  103. !DumbAxon commentStamp!
  104. I am an axon that does nothing.!
  105. !DumbAxon methodsFor: 'as yet unclassified'!
  106. add: anInterest
  107. "pass"
  108. !
  109. clean
  110. "pass"
  111. !
  112. do: aBlock
  113. "pass"
  114. ! !
  115. Axon subclass: #SimpleAxon
  116. instanceVariableNames: 'queue'
  117. package: 'Axxord'!
  118. !SimpleAxon methodsFor: 'accessing'!
  119. add: aSubscription
  120. queue add: aSubscription.
  121. ! !
  122. !SimpleAxon methodsFor: 'bookkeeping'!
  123. clean
  124. queue := queue select: [ :each | each isEnabled ]
  125. ! !
  126. !SimpleAxon methodsFor: 'enumeration'!
  127. do: aBlock
  128. queue do: aBlock
  129. ! !
  130. !SimpleAxon methodsFor: 'initialization'!
  131. initialize
  132. super initialize.
  133. queue := OrderedCollection new
  134. ! !
  135. Object subclass: #AxonInterest
  136. instanceVariableNames: 'aspect actionBlock flagged'
  137. package: 'Axxord'!
  138. !AxonInterest methodsFor: 'accessing'!
  139. aspect: anAspect block: aBlock
  140. aspect := anAspect.
  141. actionBlock := aBlock
  142. !
  143. flag
  144. flagged := true
  145. ! !
  146. !AxonInterest methodsFor: 'action'!
  147. run
  148. [ flagged := false. actionBlock value ]
  149. on: AxonOff do: [ actionBlock := nil ]
  150. ! !
  151. !AxonInterest methodsFor: 'initialization'!
  152. initialize
  153. super initialize.
  154. aspect := nil.
  155. actionBlock := nil.
  156. flagged := false.
  157. ! !
  158. !AxonInterest methodsFor: 'testing'!
  159. accepts: anAspect
  160. "Should return true if change for anAspect is relevant for this AxonInterest"
  161. self subclassResponsibility
  162. !
  163. isEnabled
  164. ^actionBlock notNil
  165. !
  166. isFlagged
  167. ^flagged
  168. ! !
  169. AxonInterest subclass: #InterestedInEqual
  170. instanceVariableNames: ''
  171. package: 'Axxord'!
  172. !InterestedInEqual methodsFor: 'testing'!
  173. accepts: anAspect
  174. ^ anAspect = aspect
  175. ! !
  176. AxonInterest subclass: #InterestedThruAxes
  177. instanceVariableNames: ''
  178. package: 'Axxord'!
  179. !InterestedThruAxes methodsFor: 'testing'!
  180. accepts: anAspect
  181. ^anAspect size <= aspect size
  182. ifTrue: [anAspect = (aspect copyFrom: 1 to: anAspect size)]
  183. ifFalse: [aspect = (anAspect copyFrom: 1 to: aspect size)]
  184. ! !
  185. AxonInterest subclass: #InterestedUpToAxes
  186. instanceVariableNames: ''
  187. package: 'Axxord'!
  188. !InterestedUpToAxes methodsFor: 'testing'!
  189. accepts: anAspect
  190. ^anAspect size <= aspect size and: [anAspect = (aspect copyFrom: 1 to: anAspect size)]
  191. ! !
  192. Error subclass: #AxonOff
  193. instanceVariableNames: ''
  194. package: 'Axxord'!
  195. !AxonOff commentStamp!
  196. Signal me from the subscription block to unsubscribe it.!
  197. !Array methodsFor: '*Axxord'!
  198. asAxisIn: anObject ifAbsent: aBlock
  199. | receiver selector result |
  200. selector := self first.
  201. receiver := anObject yourself. "JSObjectProxy hack"
  202. [ result := receiver perform: selector ]
  203. on: MessageNotUnderstood do: [ :mnu |
  204. ((mnu message selector = selector
  205. and: [ mnu receiver == receiver ])
  206. and: [ mnu message arguments isEmpty ])
  207. ifFalse: [ mnu resignal ].
  208. ^ aBlock value ].
  209. ^ result
  210. !
  211. asAxisIn: anObject ifAbsent: aBlock put: anotherObject
  212. | receiver selector arguments result |
  213. selector := self first asMutator.
  214. receiver := anObject yourself. "JSObjectProxy hack"
  215. arguments := { anotherObject }.
  216. [ result := receiver perform: selector withArguments: arguments ]
  217. on: MessageNotUnderstood do: [ :mnu |
  218. ((mnu message selector = selector
  219. and: [ mnu receiver == receiver ])
  220. and: [ mnu message arguments = arguments ])
  221. ifFalse: [ mnu resignal ].
  222. ^ aBlock value ].
  223. ^ result
  224. ! !
  225. !Number methodsFor: '*Axxord'!
  226. asAxisIn: anObject ifAbsent: aBlock
  227. (anObject respondsTo: #at:ifAbsent:)
  228. ifTrue: [ ^ anObject at: self ifAbsent: aBlock ]
  229. ifFalse: aBlock
  230. !
  231. asAxisIn: anObject ifAbsent: aBlock put: anotherObject
  232. (anObject respondsTo: #at:put:)
  233. ifTrue: [ ^ anObject at: self put: anotherObject ]
  234. ifFalse: aBlock
  235. ! !
  236. !Object methodsFor: '*Axxord'!
  237. asAxisIn: anObject ifAbsent: aBlock
  238. ^ aBlock value
  239. !
  240. asAxisIn: anObject ifAbsent: aBlock put: anotherObject
  241. ^ aBlock value
  242. !
  243. atAxes: aCollection ifAbsent: aBlock
  244. ^ aCollection inject: self into: [ :soFar :segment |
  245. segment asAxisIn: soFar ifAbsent: [ ^ aBlock value ]]
  246. !
  247. atAxes: aCollection ifAbsent: aBlock put: value
  248. | penultimate |
  249. penultimate := self atAxes: aCollection allButLast ifAbsent: aBlock.
  250. ^ aCollection last asAxisIn: penultimate ifAbsent: aBlock put: value
  251. !
  252. axes: aCollection consume: aBlock
  253. | value |
  254. value := self atAxes: aCollection ifAbsent: [ ^self ].
  255. ^ aBlock value: value
  256. !
  257. axes: aCollection transform: aBlock
  258. | value newValue |
  259. value := self atAxes: aCollection ifAbsent: [ ^self ].
  260. newValue := aBlock value: value.
  261. value == newValue ifFalse: [ self atAxes: aCollection ifAbsent: [ ^self ] put: newValue ].
  262. self registeredAxon ifNotNil: [:axon | axon changed: aCollection]
  263. !
  264. registeredAxon
  265. <inlineJS: 'return self.$axon$'>
  266. ! !
  267. !String methodsFor: '*Axxord'!
  268. asAxisIn: anObject ifAbsent: aBlock
  269. (anObject respondsTo: #at:ifAbsent:)
  270. ifTrue: [ ^ anObject at: self ifAbsent: aBlock ]
  271. ifFalse: aBlock
  272. !
  273. asAxisIn: anObject ifAbsent: aBlock put: anotherObject
  274. (anObject respondsTo: #at:put:)
  275. ifTrue: [ ^ anObject at: self put: anotherObject ]
  276. ifFalse: aBlock
  277. ! !