Platform-ImportExport.st 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208
  1. Smalltalk createPackage: 'Platform-ImportExport'!
  2. Object subclass: #AbstractExporter
  3. slots: {}
  4. package: 'Platform-ImportExport'!
  5. !AbstractExporter commentStamp!
  6. I am an abstract exporter for Amber source code.
  7. ## API
  8. Use `#exportPackage:on:` to export a given package on a Stream.!
  9. !AbstractExporter methodsFor: 'accessing'!
  10. extensionMethodsOfPackage: aPackage
  11. | result |
  12. result := OrderedCollection new.
  13. (self extensionProtocolsOfPackage: aPackage) do: [ :each |
  14. result addAll: each ownMethods ].
  15. ^ result
  16. !
  17. extensionProtocolsOfPackage: aPackage
  18. | extensionName result |
  19. extensionName := '*', aPackage name.
  20. result := OrderedCollection new.
  21. "The classes must be loaded since it is extensions only.
  22. Therefore topological sorting (dependency resolution) does not matter here.
  23. Not sorting topologically improves the speed by a number of magnitude.
  24. Not to shuffle diffs, classes are sorted by their name."
  25. (Smalltalk classes asArray sorted: [ :a :b | a name < b name ]) do: [ :each |
  26. ({each. each theMetaClass} copyWithout: nil) do: [ :behavior |
  27. (behavior protocols includes: extensionName) ifTrue: [
  28. result add: (ExportMethodProtocol name: extensionName theClass: behavior) ] ] ].
  29. ^ result
  30. ! !
  31. !AbstractExporter methodsFor: 'output'!
  32. exportPackage: aPackage on: aStream
  33. self subclassResponsibility
  34. ! !
  35. AbstractExporter subclass: #ChunkExporter
  36. slots: {}
  37. package: 'Platform-ImportExport'!
  38. !ChunkExporter commentStamp!
  39. I am an exporter dedicated to outputting Amber source code in the classic Smalltalk chunk format.
  40. I do not output any compiled code.!
  41. !ChunkExporter methodsFor: 'accessing'!
  42. extensionCategoriesOfPackage: aPackage
  43. "Issue #143: sort protocol alphabetically"
  44. | name map result |
  45. name := aPackage name.
  46. result := OrderedCollection new.
  47. (Package sortedClasses: Smalltalk classes) do: [ :each |
  48. {each. each theMetaClass} do: [ :aClass |
  49. map := Dictionary new.
  50. aClass protocolsDo: [ :category :methods |
  51. category = ('*', name) ifTrue: [ map at: category put: methods ] ].
  52. result addAll: ((map keys sorted: [ :a :b | a <= b ]) collect: [ :category |
  53. MethodCategory name: category theClass: aClass methods: (map at: category) ]) ] ].
  54. ^ result
  55. !
  56. ownCategoriesOfClass: aClass
  57. "Answer the protocols of aClass that are not package extensions"
  58. "Issue #143: sort protocol alphabetically"
  59. | map |
  60. map := Dictionary new.
  61. aClass protocolsDo: [ :each :methods |
  62. (each match: '^\*') ifFalse: [ map at: each put: methods ] ].
  63. ^ (map keys sorted: [ :a :b | a <= b ]) collect: [ :each |
  64. MethodCategory name: each theClass: aClass methods: (map at: each) ]
  65. !
  66. ownCategoriesOfMetaClass: aClass
  67. "Issue #143: sort protocol alphabetically"
  68. ^ self ownCategoriesOfClass: aClass theMetaClass
  69. !
  70. ownMethodProtocolsOfClass: aClass
  71. "Answer a collection of ExportMethodProtocol object of aClass that are not package extensions"
  72. ^ aClass ownProtocols collect: [ :each |
  73. ExportMethodProtocol name: each theClass: aClass ]
  74. ! !
  75. !ChunkExporter methodsFor: 'convenience'!
  76. chunkEscape: aString
  77. "Replace all occurrences of !! with !!!! and trim at both ends."
  78. ^ (aString replace: '!!' with: '!!!!') trimBoth
  79. ! !
  80. !ChunkExporter methodsFor: 'output'!
  81. exportBehavior: aBehavior on: aStream
  82. aBehavior exportBehaviorDefinitionTo: aStream using: self.
  83. self
  84. exportProtocols: (self ownMethodProtocolsOfClass: aBehavior)
  85. on: aStream
  86. !
  87. exportCategoryEpilogueOf: aCategory on: aStream
  88. aStream write: ' !!'; lf; lf
  89. !
  90. exportCategoryPrologueOf: aCategory on: aStream
  91. aStream
  92. write: '!!';
  93. print: aCategory theClass;
  94. write: ' methodsFor: ';
  95. print: aCategory;
  96. write: '!!'
  97. !
  98. exportDefinitionOf: aClass on: aStream
  99. "Chunk format."
  100. aStream
  101. print: aClass superclass;
  102. write: ' subclass: ';
  103. printSymbol: aClass name;
  104. lf.
  105. "aClass traitComposition
  106. ifNotEmpty: [ aStream tab; write: {'uses: '. aClass traitCompositionDefinition}; lf ]."
  107. aStream
  108. tab;
  109. write: {'slots: {'. ('. ' join: (aClass instanceVariableNames collect: #symbolPrintString)). '}'};
  110. lf;
  111. tab;
  112. write: 'package: ';
  113. print: aClass category;
  114. write: '!!';
  115. lf.
  116. aClass comment ifNotEmpty: [ aStream
  117. write: '!!'; print: aClass; write: ' commentStamp!!'; lf;
  118. write: { self chunkEscape: aClass comment. '!!' }; lf ].
  119. aStream lf
  120. !
  121. exportMetaDefinitionOf: aClass on: aStream
  122. | classIvars classTraitComposition |
  123. classIvars := aClass class instanceVariableNames.
  124. classTraitComposition := aClass class traitComposition.
  125. (classIvars notEmpty "or: [classTraitComposition notEmpty]") ifTrue: [
  126. aStream
  127. print: aClass theMetaClass.
  128. aStream space. "classTraitComposition
  129. ifEmpty: [ aStream space ]
  130. ifNotEmpty: [ aStream lf; tab; write: {'uses: '. aClass class traitCompositionDefinition}; lf; tab ]."
  131. aStream
  132. write: {'slots: {'. ('. ' join: (classIvars collect: #symbolPrintString)). '}!!'}; lf; lf ]
  133. !
  134. exportMethod: aMethod on: aStream
  135. aStream
  136. lf; lf; write: (self chunkEscape: aMethod source); lf;
  137. write: '!!'
  138. !
  139. exportPackage: aPackage on: aStream
  140. self
  141. exportPackageDefinitionOf: aPackage on: aStream;
  142. exportPackageImportsOf: aPackage on: aStream.
  143. aPackage sortedClasses do: [ :each |
  144. self exportBehavior: each on: aStream.
  145. each theMetaClass ifNotNil: [ :meta | self exportBehavior: meta on: aStream ] ].
  146. self exportPackageTraitCompositionsOf: aPackage on: aStream.
  147. self
  148. exportProtocols: (self extensionProtocolsOfPackage: aPackage)
  149. on: aStream
  150. !
  151. exportPackageDefinitionOf: aPackage on: aStream
  152. aStream
  153. write: 'Smalltalk createPackage: ';
  154. print: aPackage name;
  155. write: '!!';
  156. lf
  157. !
  158. exportPackageImportsOf: aPackage on: aStream
  159. aPackage imports ifNotEmpty: [ :imports | aStream
  160. write: '(Smalltalk packageAt: ';
  161. print: aPackage name;
  162. write: ' ifAbsent: [ self error: ';
  163. print: 'Package not created: ', aPackage name;
  164. write: { ' ]) imports: '. self chunkEscape: aPackage importsDefinition. '!!' };
  165. lf ]
  166. !
  167. exportPackageTraitCompositionsOf: aPackage on: aStream
  168. aPackage traitCompositions ifNotEmpty: [ :traitCompositions |
  169. traitCompositions keysAndValuesDo: [ :key :value | self exportTraitComposition: value of: key on: aStream ].
  170. aStream write: '!! !!'; lf; lf ]
  171. !
  172. exportProtocol: aProtocol on: aStream
  173. aProtocol ownMethods ifNotEmpty: [ :methods |
  174. self exportProtocolPrologueOf: aProtocol on: aStream.
  175. methods do: [ :method |
  176. self exportMethod: method on: aStream ].
  177. self exportProtocolEpilogueOf: aProtocol on: aStream ]
  178. !
  179. exportProtocolEpilogueOf: aProtocol on: aStream
  180. aStream write: ' !!'; lf; lf
  181. !
  182. exportProtocolPrologueOf: aProtocol on: aStream
  183. aStream
  184. write: '!!';
  185. print: aProtocol theClass;
  186. write: ' methodsFor: ';
  187. print: aProtocol name;
  188. write: '!!'
  189. !
  190. exportProtocols: aCollection on: aStream
  191. aCollection do: [ :each |
  192. self exportProtocol: each on: aStream ]
  193. !
  194. exportTraitComposition: aTraitComposition of: aBehavior on: aStream
  195. aStream
  196. print: aBehavior;
  197. write: ' setTraitComposition: ';
  198. write: aBehavior traitCompositionDefinition;
  199. write: ' asTraitComposition!!';
  200. lf
  201. !
  202. exportTraitDefinitionOf: aClass on: aStream
  203. "Chunk format."
  204. aStream
  205. write: 'Trait named: '; printSymbol: aClass name; lf.
  206. "aClass traitComposition
  207. ifNotEmpty: [ aStream tab; write: {'uses: '. aClass traitCompositionDefinition}; lf ]."
  208. aStream
  209. tab; write: 'package: '; print: aClass category; write: '!!'; lf.
  210. aClass comment ifNotEmpty: [
  211. aStream
  212. write: '!!'; print: aClass; write: ' commentStamp!!'; lf;
  213. write: { self chunkEscape: aClass comment. '!!' }; lf ].
  214. aStream lf
  215. ! !
  216. AbstractExporter subclass: #Exporter
  217. slots: {}
  218. package: 'Platform-ImportExport'!
  219. !Exporter commentStamp!
  220. I am responsible for outputting Amber code into a JavaScript string.
  221. The generated output is enough to reconstruct the exported data, including Smalltalk source code and other metadata.
  222. ## Use case
  223. I am typically used to save code outside of the Amber runtime (committing to disk, etc.).!
  224. !Exporter methodsFor: 'accessing'!
  225. ownMethodsOfClass: aClass
  226. "Issue #143: sort methods alphabetically"
  227. ^ ((aClass methodDictionary values) sorted: [ :a :b | a selector <= b selector ])
  228. reject: [ :each | (each protocol match: '^\*') ]
  229. !
  230. ownMethodsOfMetaClass: aClass
  231. "Issue #143: sort methods alphabetically"
  232. ^ self ownMethodsOfClass: aClass theMetaClass
  233. ! !
  234. !Exporter methodsFor: 'output'!
  235. exportBehavior: aBehavior on: aStream
  236. aBehavior exportBehaviorDefinitionTo: aStream using: self.
  237. aBehavior ownMethods do: [ :method |
  238. self exportMethod: method on: aStream ]
  239. !
  240. exportDefinitionOf: aClass on: aStream
  241. aStream
  242. lf;
  243. write: {
  244. '$core.addClass('.
  245. aClass name asJavaScriptSource. ', '.
  246. aClass superclass ifNil: [ 'null' ] ifNotNil: [ :superclass | superclass asJavaScriptSource ]. ', '.
  247. aClass instanceVariableNames asJavaScriptSource. ', '.
  248. aClass category asJavaScriptSource.
  249. ');' }.
  250. aClass comment ifNotEmpty: [
  251. aStream
  252. lf;
  253. write: '//>>excludeStart("ide", pragmas.excludeIdeData);'; lf;
  254. write: { aClass asJavaScriptSource. '.comment='. aClass comment crlfSanitized asJavaScriptSource. ';' }; lf;
  255. write: '//>>excludeEnd("ide");' ].
  256. aStream lf
  257. !
  258. exportMetaDefinitionOf: aClass on: aStream
  259. aStream lf.
  260. aClass theMetaClass instanceVariableNames ifNotEmpty: [ :classIvars | aStream
  261. write: { '$core.setSlots('. aClass theMetaClass asJavaScriptSource. ', '. classIvars asJavaScriptSource. ');' };
  262. lf ]
  263. !
  264. exportMethod: aMethod on: aStream
  265. aStream
  266. write: '$core.addMethod('; lf;
  267. write: '$core.method({'; lf;
  268. write: { 'selector: '. aMethod selector asJavaScriptSource. ',' }; lf;
  269. write: { 'protocol: '. aMethod protocol asJavaScriptSource. ',' }; lf;
  270. write: { 'fn: '. aMethod fn compiledSource. ',' }; lf;
  271. write: '//>>excludeStart("ide", pragmas.excludeIdeData);'; lf;
  272. write: { 'args: '. aMethod arguments asJavaScriptSource. ',' }; lf;
  273. write: { 'source: '. aMethod source asJavaScriptSource. ',' }; lf;
  274. write: { 'referencedClasses: '. aMethod referencedClasses asJavaScriptSource. ',' }; lf;
  275. write: '//>>excludeEnd("ide");'; lf;
  276. write: { 'pragmas: '. aMethod basicPragmas asJavaScriptSource. ',' }; lf;
  277. write: { 'messageSends: '. aMethod messageSends asJavaScriptSource }; lf;
  278. write: '}),'; lf;
  279. write: { aMethod methodClass asJavaScriptSource. ');' }; lf; lf
  280. !
  281. exportPackage: aPackage on: aStream
  282. self
  283. exportPackagePrologueOf: aPackage on: aStream;
  284. exportPackageDefinitionOf: aPackage on: aStream;
  285. exportPackageContextOf: aPackage on: aStream;
  286. exportPackageImportsOf: aPackage on: aStream;
  287. exportPackageTransportOf: aPackage on: aStream.
  288. aPackage sortedClasses do: [ :each |
  289. self exportBehavior: each on: aStream.
  290. each theMetaClass ifNotNil: [ :meta | self exportBehavior: meta on: aStream ] ].
  291. self exportPackageTraitCompositionsOf: aPackage on: aStream.
  292. (self extensionMethodsOfPackage: aPackage) do: [ :each |
  293. self exportMethod: each on: aStream ].
  294. self exportPackageEpilogueOf: aPackage on: aStream
  295. !
  296. exportPackageBodyBlockPrologueOf: aPackage on: aStream
  297. aStream
  298. write: 'var $core=$boot.api,nil=$boot.nilAsValue,$nil=$boot.nilAsReceiver,$recv=$boot.asReceiver,$globals=$boot.globals;'; lf
  299. !
  300. exportPackageContextOf: aPackage on: aStream
  301. aStream
  302. write: '$pkg.innerEval = function (expr) { return eval(expr); };';
  303. lf
  304. !
  305. exportPackageDefinitionOf: aPackage on: aStream
  306. aStream
  307. write: { 'var $pkg = $core.addPackage('. aPackage name asJavaScriptSource. ');' };
  308. lf
  309. !
  310. exportPackageEpilogueOf: aPackage on: aStream
  311. self subclassResponsibility
  312. !
  313. exportPackageImportsOf: aPackage on: aStream
  314. aPackage importsAsJson ifNotEmpty: [ :imports |
  315. aStream
  316. write: { '$pkg.imports = '. imports asJavaScriptSource. ';' };
  317. lf ]
  318. !
  319. exportPackagePrologueOf: aPackage on: aStream
  320. self subclassResponsibility
  321. !
  322. exportPackageTraitCompositionsOf: aPackage on: aStream
  323. aPackage traitCompositions ifNotEmpty: [ :traitCompositions |
  324. traitCompositions keysAndValuesDo: [ :key :value | self exportTraitComposition: value of: key on: aStream ].
  325. aStream lf ]
  326. !
  327. exportPackageTransportOf: aPackage on: aStream
  328. aStream
  329. write: { '$pkg.transport = '. aPackage transport asJSONString. ';' };
  330. lf
  331. !
  332. exportTraitComposition: aTraitComposition of: aBehavior on: aStream
  333. aStream write: {
  334. '$core.setTraitComposition('.
  335. aTraitComposition asJavaScriptSource.
  336. ', '.
  337. aBehavior asJavaScriptSource.
  338. ');' };
  339. lf
  340. !
  341. exportTraitDefinitionOf: aClass on: aStream
  342. aStream
  343. lf;
  344. write: {
  345. '$core.addTrait('.
  346. aClass name asJavaScriptSource. ', '.
  347. aClass category asJavaScriptSource.
  348. ');' }.
  349. aClass comment ifNotEmpty: [
  350. aStream
  351. lf;
  352. write: '//>>excludeStart("ide", pragmas.excludeIdeData);'; lf;
  353. write: { aClass asJavaScriptSource. '.comment='. aClass comment crlfSanitized asJavaScriptSource. ';' }; lf;
  354. write: '//>>excludeEnd("ide");' ].
  355. aStream lf
  356. ! !
  357. Exporter subclass: #AmdExporter
  358. slots: {#namespace}
  359. package: 'Platform-ImportExport'!
  360. !AmdExporter commentStamp!
  361. I am used to export Packages in an AMD (Asynchronous Module Definition) JavaScript format.!
  362. !AmdExporter methodsFor: 'output'!
  363. exportPackageEpilogueOf: aPackage on: aStream
  364. aStream
  365. write: '});';
  366. lf
  367. !
  368. exportPackageImportsOf: aPackage on: aStream
  369. | importsForOutput pragmaStart pragmaEnd |
  370. pragmaStart := '//>>excludeStart("imports", pragmas.excludeImports);', String lf.
  371. pragmaEnd := '//>>excludeEnd("imports");', String lf.
  372. super exportPackageImportsOf: aPackage on: aStream.
  373. importsForOutput := self importsForOutput: aPackage.
  374. importsForOutput value ifNotEmpty: [ :imports |
  375. | vars |
  376. aStream write: pragmaStart.
  377. vars := importsForOutput key.
  378. vars ifNotEmpty: [ aStream write: { 'var '. ',' join: vars. ';' }; lf ].
  379. aStream
  380. write: {
  381. '$pkg.isReady = new Promise(function (resolve, reject) { requirejs('.
  382. imports asJavaScriptSource.
  383. ', function ('.
  384. ',' join: ((1 to: vars size) collect: [ :each | '$', each asString ]).
  385. ') {'.
  386. (1 to: vars size) collect: [ :each | (vars at: each), '=$', each asString, '; ' ].
  387. 'resolve();}, reject); });' };
  388. lf;
  389. write: pragmaEnd ]
  390. !
  391. exportPackagePrologueOf: aPackage on: aStream
  392. | loadDependencies pragmaStart pragmaEnd |
  393. pragmaStart := '//>>excludeStart("imports", pragmas.excludeImports);', String lf.
  394. pragmaEnd := '//>>excludeEnd("imports");', String lf.
  395. loadDependencies := self amdNamesOfPackages: aPackage loadDependencies.
  396. aStream
  397. write: {
  398. 'define('.
  399. (#('amber/boot' 'require'), loadDependencies asArray sorted) asJavaScriptSource.
  400. ', function($boot,requirejs){"use strict";' };
  401. lf.
  402. self exportPackageBodyBlockPrologueOf: aPackage on: aStream
  403. ! !
  404. !AmdExporter methodsFor: 'private'!
  405. amdNamesOfPackages: anArray
  406. ^ (anArray
  407. select: [ :each | (self amdNamespaceOfPackage: each) notNil ])
  408. collect: [ :each | (self amdNamespaceOfPackage: each), '/', each name ]
  409. !
  410. amdNamespaceOfPackage: aPackage
  411. ^ (aPackage transport type = 'amd')
  412. ifTrue: [ aPackage transport namespace ]
  413. ifFalse: [ nil ]
  414. !
  415. importsForOutput: aPackage
  416. "Returns an association where key is list of import variables
  417. and value is list of external dependencies, with ones imported as variables
  418. put at the beginning with same order as is in key.
  419. For example imports:{'jQuery'->'jquery'. 'bootstrap'} would yield
  420. #('jQuery') -> #('jquery' 'bootstrap')"
  421. | namedImports anonImports importVarNames |
  422. namedImports := #().
  423. anonImports := #().
  424. importVarNames := #().
  425. aPackage imports do: [ :each | each isString
  426. ifTrue: [ anonImports add: each ]
  427. ifFalse: [ namedImports add: each value.
  428. importVarNames add: each key ]].
  429. ^ importVarNames -> (namedImports, anonImports)
  430. ! !
  431. Object subclass: #ChunkParser
  432. slots: {#stream. #last}
  433. package: 'Platform-ImportExport'!
  434. !ChunkParser commentStamp!
  435. I am responsible for parsing aStream contents in the chunk format.
  436. ## API
  437. ChunkParser new
  438. stream: aStream;
  439. nextChunk!
  440. !ChunkParser methodsFor: 'accessing'!
  441. last
  442. ^ last
  443. !
  444. stream: aStream
  445. stream := aStream
  446. ! !
  447. !ChunkParser methodsFor: 'reading'!
  448. nextChunk
  449. "The chunk format (Smalltalk Interchange Format or Fileout format)
  450. is a trivial format but can be a bit tricky to understand:
  451. - Uses the exclamation mark as delimiter of chunks.
  452. - Inside a chunk a normal exclamation mark must be doubled.
  453. - A non empty chunk must be a valid Smalltalk expression.
  454. - A chunk on top level with a preceding empty chunk is an instruction chunk:
  455. - The object created by the expression then takes over reading chunks.
  456. This method returns next chunk as a String (trimmed), empty String (all whitespace) or nil."
  457. | char result chunk |
  458. result := '' writeStream.
  459. [ char := stream next.
  460. char notNil ] whileTrue: [
  461. char = '!!' ifTrue: [
  462. stream peek = '!!'
  463. ifTrue: [ stream next "skipping the escape double" ]
  464. ifFalse: [ ^ last := result contents trimBoth "chunk end marker found" ]].
  465. result nextPut: char ].
  466. ^ last := nil "a chunk needs to end with !!"
  467. ! !
  468. !ChunkParser class methodsFor: 'instance creation'!
  469. on: aStream
  470. ^ self new stream: aStream
  471. ! !
  472. Object subclass: #ClassCommentReader
  473. slots: {#class}
  474. package: 'Platform-ImportExport'!
  475. !ClassCommentReader commentStamp!
  476. I provide a mechanism for retrieving class comments stored on a file.
  477. See also `ClassCategoryReader`.!
  478. !ClassCommentReader methodsFor: 'accessing'!
  479. class: aClass
  480. class := aClass
  481. ! !
  482. !ClassCommentReader methodsFor: 'fileIn'!
  483. scanFrom: aChunkParser
  484. | chunk |
  485. chunk := aChunkParser nextChunk.
  486. chunk ifNotEmpty: [
  487. self setComment: chunk ].
  488. ! !
  489. !ClassCommentReader methodsFor: 'initialization'!
  490. initialize
  491. super initialize.
  492. ! !
  493. !ClassCommentReader methodsFor: 'private'!
  494. setComment: aString
  495. class comment: aString
  496. ! !
  497. Object subclass: #ClassProtocolReader
  498. slots: {#class. #category}
  499. package: 'Platform-ImportExport'!
  500. !ClassProtocolReader commentStamp!
  501. I provide a mechanism for retrieving class descriptions stored on a file in the Smalltalk chunk format.!
  502. !ClassProtocolReader methodsFor: 'accessing'!
  503. class: aClass category: aString
  504. class := aClass.
  505. category := aString
  506. ! !
  507. !ClassProtocolReader methodsFor: 'fileIn'!
  508. scanFrom: aChunkParser
  509. | chunk |
  510. [ chunk := aChunkParser nextChunk.
  511. chunk isEmpty ] whileFalse: [
  512. self compileMethod: chunk ]
  513. ! !
  514. !ClassProtocolReader methodsFor: 'initialization'!
  515. initialize
  516. super initialize.
  517. ! !
  518. !ClassProtocolReader methodsFor: 'private'!
  519. compileMethod: aString
  520. Compiler new install: aString forClass: class protocol: category
  521. ! !
  522. Object subclass: #ExportMethodProtocol
  523. slots: {#name. #theClass}
  524. package: 'Platform-ImportExport'!
  525. !ExportMethodProtocol commentStamp!
  526. I am an abstraction for a method protocol in a class / metaclass.
  527. I know of my class, name and methods.
  528. I am used when exporting a package.!
  529. !ExportMethodProtocol methodsFor: 'accessing'!
  530. methods
  531. ^ (self theClass methodsInProtocol: self name)
  532. sorted: [ :a :b | a selector <= b selector ]
  533. !
  534. name
  535. ^ name
  536. !
  537. name: aString
  538. name := aString
  539. !
  540. ownMethods
  541. ^ (self theClass ownMethodsInProtocol: self name)
  542. sorted: [ :a :b | a selector <= b selector ]
  543. !
  544. theClass
  545. ^ theClass
  546. !
  547. theClass: aClass
  548. theClass := aClass
  549. ! !
  550. !ExportMethodProtocol class methodsFor: 'instance creation'!
  551. name: aString theClass: aClass
  552. ^ self new
  553. name: aString;
  554. theClass: aClass;
  555. yourself
  556. ! !
  557. Object subclass: #Importer
  558. slots: {#lastSection. #lastChunk}
  559. package: 'Platform-ImportExport'!
  560. !Importer commentStamp!
  561. I can import Amber code from a string in the chunk format.
  562. ## API
  563. Importer new import: aString!
  564. !Importer methodsFor: 'accessing'!
  565. lastChunk
  566. ^ lastChunk
  567. !
  568. lastSection
  569. ^ lastSection
  570. ! !
  571. !Importer methodsFor: 'fileIn'!
  572. import: aStream
  573. | chunk result parser lastEmpty |
  574. parser := ChunkParser on: aStream.
  575. lastEmpty := false.
  576. lastSection := 'n/a, not started'.
  577. lastChunk := nil.
  578. [
  579. [ chunk := parser nextChunk.
  580. chunk isNil ] whileFalse: [
  581. chunk
  582. ifEmpty: [ lastEmpty := true ]
  583. ifNotEmpty: [
  584. lastSection := chunk.
  585. result := Compiler new evaluateExpression: chunk.
  586. lastEmpty
  587. ifTrue: [
  588. lastEmpty := false.
  589. result scanFrom: parser ]] ].
  590. lastSection := 'n/a, finished'
  591. ] on: Error do: [:e | lastChunk := parser last. e pass ].
  592. ! !
  593. Error subclass: #PackageCommitError
  594. slots: {}
  595. package: 'Platform-ImportExport'!
  596. !PackageCommitError commentStamp!
  597. I get signaled when an attempt to commit a package has failed.!
  598. Object subclass: #PackageHandler
  599. slots: {}
  600. package: 'Platform-ImportExport'!
  601. !PackageHandler commentStamp!
  602. I am responsible for handling package loading and committing.
  603. I should not be used directly. Instead, use the corresponding `Package` methods.!
  604. !PackageHandler methodsFor: 'accessing'!
  605. chunkContentsFor: aPackage
  606. ^ String streamContents: [ :str |
  607. self chunkExporter exportPackage: aPackage on: str ]
  608. !
  609. chunkExporterClass
  610. ^ ChunkExporter
  611. !
  612. commitPathJsFor: aPackage
  613. self subclassResponsibility
  614. !
  615. commitPathStFor: aPackage
  616. self subclassResponsibility
  617. !
  618. contentsFor: aPackage
  619. ^ String streamContents: [ :str |
  620. self exporter exportPackage: aPackage on: str ]
  621. !
  622. exporterClass
  623. self subclassResponsibility
  624. !
  625. setPath: aString forPackage: aPackage
  626. self subclassResponsibility
  627. ! !
  628. !PackageHandler methodsFor: 'committing'!
  629. commit: aPackage
  630. self
  631. commit: aPackage
  632. onSuccess: []
  633. onError: [ :error |
  634. PackageCommitError new
  635. messageText: 'Commiting failed with reason: "' , (error responseText) , '"';
  636. signal ]
  637. !
  638. commit: aPackage onSuccess: aBlock onError: anotherBlock
  639. self
  640. commitJsFileFor: aPackage
  641. onSuccess: [
  642. self
  643. commitStFileFor: aPackage
  644. onSuccess: [ aPackage beClean. aBlock value ]
  645. onError: anotherBlock ]
  646. onError: anotherBlock
  647. !
  648. commitJsFileFor: aPackage onSuccess: aBlock onError: anotherBlock
  649. self
  650. ajaxPutAt: (self commitPathJsFor: aPackage), '/', aPackage name, '.js'
  651. data: (self contentsFor: aPackage)
  652. onSuccess: aBlock
  653. onError: anotherBlock
  654. !
  655. commitStFileFor: aPackage onSuccess: aBlock onError: anotherBlock
  656. self
  657. ajaxPutAt: (self commitPathStFor: aPackage), '/', aPackage name, '.st'
  658. data: (self chunkContentsFor: aPackage)
  659. onSuccess: aBlock
  660. onError: anotherBlock
  661. ! !
  662. !PackageHandler methodsFor: 'error handling'!
  663. onCommitError: anError
  664. PackageCommitError new
  665. messageText: 'Commiting failed with reason: "' , (anError responseText) , '"';
  666. signal
  667. ! !
  668. !PackageHandler methodsFor: 'factory'!
  669. chunkExporter
  670. ^ self chunkExporterClass new
  671. !
  672. exporter
  673. ^ self exporterClass new
  674. ! !
  675. !PackageHandler methodsFor: 'loading'!
  676. load: aPackage
  677. "Should return a TThenable"
  678. self subclassResponsibility
  679. ! !
  680. !PackageHandler methodsFor: 'private'!
  681. ajaxPutAt: aURL data: aString onSuccess: aBlock onError: anotherBlock
  682. | xhr |
  683. xhr := Platform newXhr.
  684. xhr open: 'PUT' url: aURL async: true.
  685. xhr onreadystatechange: [
  686. xhr readyState = 4 ifTrue: [
  687. (xhr status >= 200 and: [ xhr status < 300 ])
  688. ifTrue: aBlock
  689. ifFalse: anotherBlock ]].
  690. xhr send: aString
  691. ! !
  692. PackageHandler subclass: #AmdPackageHandler
  693. slots: {}
  694. package: 'Platform-ImportExport'!
  695. !AmdPackageHandler commentStamp!
  696. I am responsible for handling package loading and committing.
  697. I should not be used directly. Instead, use the corresponding `Package` methods.!
  698. !AmdPackageHandler methodsFor: 'accessing'!
  699. commitPathJsFor: aPackage
  700. ^ self toUrl: (self namespaceFor: aPackage)
  701. !
  702. commitPathStFor: aPackage
  703. "If _source is not mapped, .st will be committed to .js path.
  704. It is recommended not to use _source as it can be deprecated."
  705. | path pathWithout |
  706. path := self toUrl: (self namespaceFor: aPackage), '/_source'.
  707. pathWithout := self commitPathJsFor: aPackage.
  708. ^ path = (pathWithout, '/_source') ifTrue: [ pathWithout ] ifFalse: [ path ]
  709. !
  710. exporterClass
  711. ^ AmdExporter
  712. !
  713. setPath: aString forPackage: aPackage
  714. "Set the path the the package's `namespace`"
  715. "Smalltalk amdRequire
  716. ifNil: [ self error: 'AMD loader not present' ]
  717. ifNotNil: [ :require |"
  718. require provided config: #{
  719. 'paths' -> #{
  720. (self namespaceFor: aPackage) -> aString
  721. }
  722. }
  723. "]"
  724. ! !
  725. !AmdPackageHandler methodsFor: 'committing'!
  726. namespaceFor: aPackage
  727. ^ aPackage transport namespace
  728. ! !
  729. !AmdPackageHandler methodsFor: 'loading'!
  730. load: aPackage
  731. ^ Promise new: [ :model |
  732. Smalltalk amdRequire
  733. ifNil: [ self error: 'AMD loader not present' ]
  734. ifNotNil: [ :require |
  735. require
  736. value: { (self namespaceFor: aPackage), '/', aPackage name }
  737. value: [ :result | model value: result ]
  738. value: [ :error | model signal: error ] ] ]
  739. ! !
  740. !AmdPackageHandler methodsFor: 'private'!
  741. toUrl: aString
  742. ^ Smalltalk amdRequire
  743. ifNil: [ self error: 'AMD loader not present' ]
  744. ifNotNil: [ :require | require provided toUrl: aString ]
  745. ! !
  746. !AmdPackageHandler class methodsFor: 'commit paths'!
  747. defaultNamespace
  748. ^ Smalltalk defaultAmdNamespace
  749. !
  750. defaultNamespace: aString
  751. Smalltalk defaultAmdNamespace: aString
  752. ! !
  753. Object subclass: #PackageTransport
  754. slots: {#package}
  755. package: 'Platform-ImportExport'!
  756. !PackageTransport commentStamp!
  757. I represent the transport mechanism used to commit a package.
  758. My concrete subclasses have a `#handler` to which committing is delegated.!
  759. !PackageTransport methodsFor: 'accessing'!
  760. commitHandlerClass
  761. self subclassResponsibility
  762. !
  763. definition
  764. ^ ''
  765. !
  766. package
  767. ^ package
  768. !
  769. package: aPackage
  770. package := aPackage
  771. !
  772. type
  773. ^ self class type
  774. ! !
  775. !PackageTransport methodsFor: 'actions'!
  776. setPath: aString
  777. "Set the commit path for the package"
  778. self commitHandler setPath: aString forPackage: package
  779. ! !
  780. !PackageTransport methodsFor: 'committing'!
  781. commit
  782. self commitHandler commit: self package
  783. !
  784. commitOnSuccess: aBlock onError: anotherBlock
  785. self commitHandler
  786. commit: self package
  787. onSuccess: aBlock
  788. onError: anotherBlock
  789. ! !
  790. !PackageTransport methodsFor: 'converting'!
  791. asJavaScriptObject
  792. ^ #{ 'type' -> self type }
  793. ! !
  794. !PackageTransport methodsFor: 'factory'!
  795. commitHandler
  796. ^ self commitHandlerClass new
  797. ! !
  798. !PackageTransport methodsFor: 'initialization'!
  799. setupFromJson: anObject
  800. "no op. override if needed in subclasses"
  801. ! !
  802. !PackageTransport methodsFor: 'loading'!
  803. load
  804. ^ (self commitHandler load: self package)
  805. then: [ Smalltalk postLoad ]
  806. ! !
  807. PackageTransport class slots: {#registry}!
  808. !PackageTransport class methodsFor: 'accessing'!
  809. classRegisteredFor: aString
  810. ^ registry at: aString
  811. !
  812. defaultType
  813. ^ AmdPackageTransport type
  814. !
  815. type
  816. "Override in subclasses"
  817. ^ nil
  818. ! !
  819. !PackageTransport class methodsFor: 'initialization'!
  820. initialize
  821. super initialize.
  822. self == PackageTransport
  823. ifTrue: [ registry := #{} ]
  824. ifFalse: [ self register ]
  825. ! !
  826. !PackageTransport class methodsFor: 'instance creation'!
  827. for: aString
  828. ^ (self classRegisteredFor: aString) new
  829. !
  830. fromJson: anObject
  831. anObject ifNil: [ ^ self for: self defaultType ].
  832. ^ (self for: anObject type)
  833. setupFromJson: anObject;
  834. yourself
  835. ! !
  836. !PackageTransport class methodsFor: 'registration'!
  837. register
  838. PackageTransport register: self
  839. !
  840. register: aClass
  841. aClass type ifNotNil: [
  842. registry at: aClass type put: aClass ]
  843. ! !
  844. PackageTransport subclass: #AmdPackageTransport
  845. slots: {#namespace}
  846. package: 'Platform-ImportExport'!
  847. !AmdPackageTransport commentStamp!
  848. I am the default transport for committing packages.
  849. See `AmdExporter` and `AmdPackageHandler`.!
  850. !AmdPackageTransport methodsFor: 'accessing'!
  851. commitHandlerClass
  852. ^ AmdPackageHandler
  853. !
  854. definition
  855. ^ String streamContents: [ :stream | stream
  856. write: { self class name. ' namespace: ' }; print: self namespace ]
  857. !
  858. namespace
  859. ^ namespace ifNil: [ self defaultNamespace ]
  860. !
  861. namespace: aString
  862. namespace := aString
  863. ! !
  864. !AmdPackageTransport methodsFor: 'converting'!
  865. asJavaScriptObject
  866. ^ super asJavaScriptObject
  867. at: 'amdNamespace' put: self namespace;
  868. yourself
  869. ! !
  870. !AmdPackageTransport methodsFor: 'defaults'!
  871. defaultNamespace
  872. ^ Smalltalk defaultAmdNamespace
  873. ! !
  874. !AmdPackageTransport methodsFor: 'initialization'!
  875. setupFromJson: anObject
  876. self namespace: (anObject at: 'amdNamespace')
  877. ! !
  878. !AmdPackageTransport methodsFor: 'printing'!
  879. printOn: aStream
  880. super printOn: aStream.
  881. aStream
  882. nextPutAll: ' (AMD Namespace: ';
  883. nextPutAll: self namespace;
  884. nextPutAll: ')'
  885. ! !
  886. !AmdPackageTransport class methodsFor: 'accessing'!
  887. type
  888. ^ 'amd'
  889. ! !
  890. !AmdPackageTransport class methodsFor: 'instance creation'!
  891. namespace: aString
  892. ^ self new
  893. namespace: aString;
  894. yourself
  895. ! !
  896. !Class methodsFor: '*Platform-ImportExport'!
  897. exportBehaviorDefinitionTo: aStream using: anExporter
  898. anExporter exportDefinitionOf: self on: aStream
  899. ! !
  900. !Metaclass methodsFor: '*Platform-ImportExport'!
  901. exportBehaviorDefinitionTo: aStream using: anExporter
  902. anExporter exportMetaDefinitionOf: self instanceClass on: aStream
  903. ! !
  904. !Package methodsFor: '*Platform-ImportExport'!
  905. commit
  906. ^ self transport commit
  907. !
  908. load
  909. ^ self transport load
  910. !
  911. loadFromNamespace: aString
  912. ^ self transport
  913. namespace: aString;
  914. load
  915. ! !
  916. !Package class methodsFor: '*Platform-ImportExport'!
  917. load: aPackageName
  918. ^ (self named: aPackageName) load
  919. !
  920. load: aPackageName fromNamespace: aString
  921. ^ (self named: aPackageName) loadFromNamespace: aString
  922. ! !
  923. !TBehaviorProvider methodsFor: '*Platform-ImportExport'!
  924. methodsFor: aString
  925. ^ ClassProtocolReader new
  926. class: self category: aString;
  927. yourself
  928. !
  929. methodsFor: aString stamp: aStamp
  930. "Added for file-in compatibility, ignores stamp."
  931. ^ self methodsFor: aString
  932. ! !
  933. !TMasterBehavior methodsFor: '*Platform-ImportExport'!
  934. commentStamp
  935. ^ ClassCommentReader new
  936. class: self;
  937. yourself
  938. !
  939. commentStamp: aStamp prior: prior
  940. ^ self commentStamp
  941. ! !
  942. !Trait methodsFor: '*Platform-ImportExport'!
  943. exportBehaviorDefinitionTo: aStream using: anExporter
  944. anExporter exportTraitDefinitionOf: self on: aStream
  945. ! !