Importer-Exporter.st 27 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100
  1. Smalltalk current createPackage: 'Importer-Exporter'!
  2. Object subclass: #AbstractExporter
  3. instanceVariableNames: ''
  4. package: 'Importer-Exporter'!
  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. extensionProtocolsOfPackage: aPackage
  11. | extensionName result |
  12. extensionName := '*', aPackage name.
  13. result := OrderedCollection new.
  14. "The classes must be loaded since it is extensions only.
  15. Therefore sorting (dependency resolution) does not matter here.
  16. Not sorting improves the speed by a number of magnitude."
  17. Smalltalk current classes do: [ :each |
  18. {each. each class} do: [ :behavior |
  19. (behavior protocols includes: extensionName) ifTrue: [
  20. result add: (ExportMethodProtocol name: extensionName theClass: behavior) ] ] ].
  21. ^result
  22. !
  23. extensionMethodsOfPackage: aPackage
  24. | result |
  25. result := OrderedCollection new.
  26. (self extensionProtocolsOfPackage: aPackage) do: [ :each |
  27. result addAll: each methods ].
  28. ^ result
  29. ! !
  30. !AbstractExporter methodsFor: 'convenience'!
  31. chunkEscape: aString
  32. "Replace all occurrences of !! with !!!! and trim at both ends."
  33. ^(aString replace: '!!' with: '!!!!') trimBoth
  34. !
  35. classNameFor: aClass
  36. ^aClass isMetaclass
  37. ifTrue: [ aClass instanceClass name, ' class' ]
  38. ifFalse: [
  39. aClass isNil
  40. ifTrue: [ 'nil' ]
  41. ifFalse: [ aClass name ] ]
  42. ! !
  43. !AbstractExporter methodsFor: 'output'!
  44. exportPackage: aPackage on: aStream
  45. self subclassResponsibility
  46. ! !
  47. AbstractExporter class instanceVariableNames: 'default'!
  48. !AbstractExporter class methodsFor: 'instance creation'!
  49. default
  50. ^ default ifNil: [ default := self new ]
  51. ! !
  52. AbstractExporter subclass: #ChunkExporter
  53. instanceVariableNames: ''
  54. package: 'Importer-Exporter'!
  55. !ChunkExporter commentStamp!
  56. I am an exporter dedicated to outputting Amber source code in the classic Smalltalk chunk format.
  57. I do not output any compiled code.!
  58. !ChunkExporter methodsFor: 'accessing'!
  59. extensionCategoriesOfPackage: aPackage
  60. "Issue #143: sort protocol alphabetically"
  61. | name map result |
  62. name := aPackage name.
  63. result := OrderedCollection new.
  64. (Package sortedClasses: Smalltalk current classes) do: [ :each |
  65. {each. each class} do: [ :aClass |
  66. map := Dictionary new.
  67. aClass protocolsDo: [ :category :methods |
  68. category = ('*', name) ifTrue: [ map at: category put: methods ] ].
  69. result addAll: ((map keys sorted: [:a :b | a <= b ]) collect: [ :category |
  70. MethodCategory name: category theClass: aClass methods: (map at: category) ]) ] ].
  71. ^result
  72. !
  73. methodsOfCategory: aCategory
  74. "Issue #143: sort methods alphabetically"
  75. ^(aCategory methods) sorted: [ :a :b | a selector <= b selector ]
  76. !
  77. ownCategoriesOfClass: aClass
  78. "Answer the protocols of aClass that are not package extensions"
  79. "Issue #143: sort protocol alphabetically"
  80. | map |
  81. map := Dictionary new.
  82. aClass protocolsDo: [ :each :methods |
  83. (each match: '^\*') ifFalse: [ map at: each put: methods ] ].
  84. ^(map keys sorted: [:a :b | a <= b ]) collect: [ :each |
  85. MethodCategory name: each theClass: aClass methods: (map at: each) ]
  86. !
  87. ownCategoriesOfMetaClass: aClass
  88. "Issue #143: sort protocol alphabetically"
  89. ^self ownCategoriesOfClass: aClass class
  90. !
  91. ownMethodProtocolsOfClass: aClass
  92. "Answer a collection of ExportMethodProtocol object of aClass that are not package extensions"
  93. ^ aClass ownProtocols collect: [ :each |
  94. ExportMethodProtocol name: each theClass: aClass ]
  95. ! !
  96. !ChunkExporter methodsFor: 'fileOut'!
  97. recipe
  98. "Export a given package."
  99. | exportCategoryRecipe |
  100. exportCategoryRecipe := {
  101. self -> #exportCategoryPrologueOf:on:.
  102. {
  103. self -> #methodsOfCategory:.
  104. self -> #exportMethod:on: }.
  105. self -> #exportCategoryEpilogueOf:on: }.
  106. ^{
  107. self -> #exportPackageDefinitionOf:on:.
  108. {
  109. PluggableExporter -> #ownClassesOfPackage:.
  110. self -> #exportDefinitionOf:on:.
  111. { self -> #ownCategoriesOfClass: }, exportCategoryRecipe.
  112. self -> #exportMetaDefinitionOf:on:.
  113. { self -> #ownCategoriesOfMetaClass: }, exportCategoryRecipe }.
  114. { self -> #extensionCategoriesOfPackage: }, exportCategoryRecipe
  115. }
  116. ! !
  117. !ChunkExporter methodsFor: 'output'!
  118. exportCategoryEpilogueOf: aCategory on: aStream
  119. aStream nextPutAll: ' !!'; lf; lf
  120. !
  121. exportCategoryPrologueOf: aCategory on: aStream
  122. aStream
  123. nextPutAll: '!!', (self classNameFor: aCategory theClass);
  124. nextPutAll: ' methodsFor: ''', aCategory name, '''!!'
  125. !
  126. exportDefinitionOf: aClass on: aStream
  127. "Chunk format."
  128. aStream
  129. nextPutAll: (self classNameFor: aClass superclass);
  130. nextPutAll: ' subclass: #', (self classNameFor: aClass); lf;
  131. tab; nextPutAll: 'instanceVariableNames: '''.
  132. aClass instanceVariableNames
  133. do: [:each | aStream nextPutAll: each]
  134. separatedBy: [aStream nextPutAll: ' '].
  135. aStream
  136. nextPutAll: ''''; lf;
  137. tab; nextPutAll: 'package: ''', aClass category, '''!!'; lf.
  138. aClass comment notEmpty ifTrue: [
  139. aStream
  140. nextPutAll: '!!', (self classNameFor: aClass), ' commentStamp!!';lf;
  141. nextPutAll: (self chunkEscape: aClass comment), '!!';lf].
  142. aStream lf
  143. !
  144. exportMetaDefinitionOf: aClass on: aStream
  145. aClass class instanceVariableNames isEmpty ifFalse: [
  146. aStream
  147. nextPutAll: (self classNameFor: aClass class);
  148. nextPutAll: ' instanceVariableNames: '''.
  149. aClass class instanceVariableNames
  150. do: [:each | aStream nextPutAll: each]
  151. separatedBy: [aStream nextPutAll: ' '].
  152. aStream
  153. nextPutAll: '''!!'; lf; lf]
  154. !
  155. exportMethod: aMethod on: aStream
  156. aStream
  157. lf; lf; nextPutAll: (self chunkEscape: aMethod source); lf;
  158. nextPutAll: '!!'
  159. !
  160. exportPackageDefinitionOf: aPackage on: aStream
  161. aStream
  162. nextPutAll: 'Smalltalk current createPackage: ''', aPackage name, '''!!';
  163. lf
  164. !
  165. exportProtocolEpilogueOf: aProtocol on: aStream
  166. aStream nextPutAll: ' !!'; lf; lf
  167. !
  168. exportProtocolPrologueOf: aProtocol on: aStream
  169. aStream
  170. nextPutAll: '!!', (self classNameFor: aProtocol theClass);
  171. nextPutAll: ' methodsFor: ''', aProtocol name, '''!!'
  172. !
  173. exportProtocol: aProtocol on: aStream
  174. self exportProtocolPrologueOf: aProtocol on: aStream.
  175. aProtocol methods do: [ :method |
  176. self exportMethod: method on: aStream ].
  177. self exportProtocolEpilogueOf: aProtocol on: aStream
  178. !
  179. exportProtocols: aCollection on: aStream
  180. aCollection do: [ :each |
  181. self exportProtocol: each on: aStream ]
  182. !
  183. exportPackage: aPackage on: aStream
  184. self exportPackageDefinitionOf: aPackage on: aStream.
  185. aPackage sortedClasses do: [ :each |
  186. self exportDefinitionOf: each on: aStream.
  187. self
  188. exportProtocols: (self ownMethodProtocolsOfClass: each)
  189. on: aStream.
  190. self exportMetaDefinitionOf: each on: aStream.
  191. self
  192. exportProtocols: (self ownMethodProtocolsOfClass: each class)
  193. on: aStream ].
  194. self
  195. exportProtocols: (self extensionProtocolsOfPackage: aPackage)
  196. on: aStream
  197. ! !
  198. AbstractExporter subclass: #Exporter
  199. instanceVariableNames: ''
  200. package: 'Importer-Exporter'!
  201. !Exporter commentStamp!
  202. I am responsible for outputting Amber code into a JavaScript string.
  203. The generated output is enough to reconstruct the exported data, including Smalltalk source code and other metadata.
  204. ## Use case
  205. I am typically used to save code outside of the Amber runtime (committing to disk, etc.).!
  206. !Exporter methodsFor: 'accessing'!
  207. ownMethodsOfClass: aClass
  208. "Issue #143: sort methods alphabetically"
  209. ^((aClass methodDictionary values) sorted: [:a :b | a selector <= b selector])
  210. reject: [:each | (each category match: '^\*')]
  211. !
  212. ownMethodsOfMetaClass: aClass
  213. "Issue #143: sort methods alphabetically"
  214. ^self ownMethodsOfClass: aClass class
  215. ! !
  216. !Exporter methodsFor: 'convenience'!
  217. classNameFor: aClass
  218. ^aClass isMetaclass
  219. ifTrue: [ aClass instanceClass name, '.klass' ]
  220. ifFalse: [
  221. aClass isNil
  222. ifTrue: [ 'nil' ]
  223. ifFalse: [ aClass name ] ]
  224. ! !
  225. !Exporter methodsFor: 'fileOut'!
  226. amdRecipe
  227. "Export a given package with amd transport type."
  228. | result |
  229. result := self recipe.
  230. result first key: AmdExporter.
  231. result last key: AmdExporter.
  232. ^result
  233. !
  234. recipe
  235. "Export a given package."
  236. ^{
  237. self -> #exportPackagePrologueOf:on:.
  238. self -> #exportPackageDefinitionOf:on:.
  239. self -> #exportPackageTransportOf:on:.
  240. {
  241. PluggableExporter -> #ownClassesOfPackage:.
  242. self -> #exportDefinitionOf:on:.
  243. {
  244. self -> #ownMethodsOfClass:.
  245. self -> #exportMethod:on: }.
  246. self -> #exportMetaDefinitionOf:on:.
  247. {
  248. self -> #ownMethodsOfMetaClass:.
  249. self -> #exportMethod:on: } }.
  250. {
  251. self -> #extensionMethodsOfPackage:.
  252. self -> #exportMethod:on: }.
  253. self -> #exportPackageEpilogueOf:on:
  254. }
  255. ! !
  256. !Exporter methodsFor: 'output'!
  257. exportDefinitionOf: aClass on: aStream
  258. aStream
  259. lf;
  260. nextPutAll: 'smalltalk.addClass(';
  261. nextPutAll: '''', (self classNameFor: aClass), ''', ';
  262. nextPutAll: 'smalltalk.', (self classNameFor: aClass superclass);
  263. nextPutAll: ', ['.
  264. aClass instanceVariableNames
  265. do: [:each | aStream nextPutAll: '''', each, '''']
  266. separatedBy: [aStream nextPutAll: ', '].
  267. aStream
  268. nextPutAll: '], ''';
  269. nextPutAll: aClass category, '''';
  270. nextPutAll: ');'.
  271. aClass comment notEmpty ifTrue: [
  272. aStream
  273. lf;
  274. nextPutAll: 'smalltalk.';
  275. nextPutAll: (self classNameFor: aClass);
  276. nextPutAll: '.comment=';
  277. nextPutAll: aClass comment asJavascript;
  278. nextPutAll: ';'].
  279. aStream lf
  280. !
  281. exportMetaDefinitionOf: aClass on: aStream
  282. aStream lf.
  283. aClass class instanceVariableNames isEmpty ifFalse: [
  284. aStream
  285. nextPutAll: 'smalltalk.', (self classNameFor: aClass class);
  286. nextPutAll: '.iVarNames = ['.
  287. aClass class instanceVariableNames
  288. do: [:each | aStream nextPutAll: '''', each, '''']
  289. separatedBy: [aStream nextPutAll: ','].
  290. aStream nextPutAll: '];', String lf]
  291. !
  292. exportMethod: aMethod on: aStream
  293. aStream
  294. nextPutAll: 'smalltalk.addMethod(';lf;
  295. "nextPutAll: aMethod selector asSelector asJavascript, ',';lf;"
  296. nextPutAll: 'smalltalk.method({';lf;
  297. nextPutAll: 'selector: ', aMethod selector asJavascript, ',';lf;
  298. nextPutAll: 'category: ''', aMethod category, ''',';lf;
  299. nextPutAll: 'fn: ', aMethod fn compiledSource, ',';lf;
  300. nextPutAll: 'args: ', aMethod arguments asJavascript, ','; lf;
  301. nextPutAll: 'source: ', aMethod source asJavascript, ',';lf;
  302. nextPutAll: 'messageSends: ', aMethod messageSends asJavascript, ',';lf;
  303. nextPutAll: 'referencedClasses: ', aMethod referencedClasses asJavascript.
  304. aStream
  305. lf;
  306. nextPutAll: '}),';lf;
  307. nextPutAll: 'smalltalk.', (self classNameFor: aMethod methodClass);
  308. nextPutAll: ');';lf;lf
  309. !
  310. exportPackageDefinitionOf: aPackage on: aStream
  311. aStream
  312. nextPutAll: 'smalltalk.addPackage(';
  313. nextPutAll: '''', aPackage name, ''');';
  314. lf
  315. !
  316. exportPackageEpilogueOf: aPackage on: aStream
  317. aStream
  318. nextPutAll: '})(global_smalltalk,global_nil,global__st);';
  319. lf
  320. !
  321. exportPackagePrologueOf: aPackage on: aStream
  322. aStream
  323. nextPutAll: '(function(smalltalk,nil,_st){';
  324. lf
  325. !
  326. exportPackageTransportOf: aPackage on: aStream
  327. | json |
  328. json := aPackage transportJson.
  329. json = 'null' ifFalse: [
  330. aStream
  331. nextPutAll: 'smalltalk.packages[';
  332. nextPutAll: aPackage name asJavascript;
  333. nextPutAll: '].transport = ';
  334. nextPutAll: json;
  335. nextPutAll: ';';
  336. lf ]
  337. !
  338. exportPackage: aPackage on: aStream
  339. self
  340. exportPackagePrologueOf: aPackage on: aStream;
  341. exportPackageDefinitionOf: aPackage on: aStream;
  342. exportPackageTransportOf: aPackage on: aStream.
  343. aPackage sortedClasses do: [ :each |
  344. self exportDefinitionOf: each on: aStream.
  345. each ownMethods do: [ :method |
  346. self exportMethod: method on: aStream ].
  347. self exportMetaDefinitionOf: each on: aStream.
  348. each class ownMethods do: [ :method |
  349. self exportMethod: method on: aStream ] ].
  350. (self extensionMethodsOfPackage: aPackage) do: [ :each |
  351. self exportMethod: each on: aStream ].
  352. self exportPackageEpilogueOf: aPackage on: aStream
  353. ! !
  354. Exporter subclass: #AmdExporter
  355. instanceVariableNames: ''
  356. package: 'Importer-Exporter'!
  357. !AmdExporter methodsFor: 'output'!
  358. exportPackageEpilogueOf: aPackage on: aStream
  359. aStream
  360. nextPutAll: '});';
  361. lf
  362. !
  363. exportPackagePrologueOf: aPackage on: aStream
  364. aStream
  365. nextPutAll: 'define("';
  366. nextPutAll: (aPackage amdNamespace ifNil: [ 'amber' ]); "ifNil: only for LegacyPH, it should not happen with AmdPH"
  367. nextPutAll: '/';
  368. nextPutAll: aPackage name;
  369. nextPutAll: '", ';
  370. nextPutAll: (#('amber_vm/smalltalk' 'amber_vm/nil' 'amber_vm/_st'), (self amdNamesOfPackages: aPackage loadDependencies)) asJavascript;
  371. nextPutAll: ', function(smalltalk,nil,_st){';
  372. lf
  373. ! !
  374. !AmdExporter methodsFor: 'private'!
  375. amdNamesOfPackages: anArray
  376. ^ (anArray
  377. select: [ :each | each amdNamespace notNil ])
  378. collect: [ :each | each amdNamespace, '/', each name ]
  379. ! !
  380. !AmdExporter class methodsFor: 'exporting-output'!
  381. exportPackageEpilogueOf: aPackage on: aStream
  382. aStream
  383. nextPutAll: '});';
  384. lf
  385. !
  386. exportPackagePrologueOf: aPackage on: aStream
  387. aStream
  388. nextPutAll: 'define("';
  389. nextPutAll: (aPackage amdNamespace ifNil: [ 'amber' ]); "ifNil: only for LegacyPH, it should not happen with AmdPH"
  390. nextPutAll: '/';
  391. nextPutAll: aPackage name;
  392. nextPutAll: '", ';
  393. nextPutAll: (#('amber_vm/smalltalk' 'amber_vm/nil' 'amber_vm/_st'), (self amdNamesOfPackages: aPackage loadDependencies)) asJavascript;
  394. nextPutAll: ', function(smalltalk,nil,_st){';
  395. lf
  396. ! !
  397. !AmdExporter class methodsFor: 'private'!
  398. amdNamesOfPackages: anArray
  399. | deps depNames |
  400. ^(anArray
  401. select: [ :each | each amdNamespace notNil ])
  402. collect: [ :each | each amdNamespace, '/', each name ]
  403. ! !
  404. Exporter subclass: #StrippedExporter
  405. instanceVariableNames: ''
  406. package: 'Importer-Exporter'!
  407. !StrippedExporter commentStamp!
  408. I export Amber code into a JavaScript string, but without any optional associated data like the Amber source code.!
  409. !StrippedExporter methodsFor: 'output'!
  410. exportDefinitionOf: aClass on: aStream
  411. aStream
  412. lf;
  413. nextPutAll: 'smalltalk.addClass(';
  414. nextPutAll: '''', (self classNameFor: aClass), ''', ';
  415. nextPutAll: 'smalltalk.', (self classNameFor: aClass superclass);
  416. nextPutAll: ', ['.
  417. aClass instanceVariableNames
  418. do: [:each | aStream nextPutAll: '''', each, '''']
  419. separatedBy: [aStream nextPutAll: ', '].
  420. aStream
  421. nextPutAll: '], ''';
  422. nextPutAll: aClass category, '''';
  423. nextPutAll: ');'.
  424. aStream lf
  425. !
  426. exportMethod: aMethod on: aStream
  427. aStream
  428. nextPutAll: 'smalltalk.addMethod(';lf;
  429. "nextPutAll: aMethod selector asSelector asJavascript, ',';lf;"
  430. nextPutAll: 'smalltalk.method({';lf;
  431. nextPutAll: 'selector: ', aMethod selector asJavascript, ',';lf;
  432. nextPutAll: 'fn: ', aMethod fn compiledSource, ',';lf;
  433. nextPutAll: 'messageSends: ', aMethod messageSends asJavascript;
  434. nextPutAll: '}),';lf;
  435. nextPutAll: 'smalltalk.', (self classNameFor: aMethod methodClass);
  436. nextPutAll: ');';lf;lf
  437. ! !
  438. Object subclass: #ChunkParser
  439. instanceVariableNames: 'stream'
  440. package: 'Importer-Exporter'!
  441. !ChunkParser commentStamp!
  442. I am responsible for parsing aStream contents in the chunk format.
  443. ## API
  444. ChunkParser new
  445. stream: aStream;
  446. nextChunk!
  447. !ChunkParser methodsFor: 'accessing'!
  448. stream: aStream
  449. stream := aStream
  450. ! !
  451. !ChunkParser methodsFor: 'reading'!
  452. nextChunk
  453. "The chunk format (Smalltalk Interchange Format or Fileout format)
  454. is a trivial format but can be a bit tricky to understand:
  455. - Uses the exclamation mark as delimiter of chunks.
  456. - Inside a chunk a normal exclamation mark must be doubled.
  457. - A non empty chunk must be a valid Smalltalk expression.
  458. - A chunk on top level with a preceding empty chunk is an instruction chunk:
  459. - The object created by the expression then takes over reading chunks.
  460. This method returns next chunk as a String (trimmed), empty String (all whitespace) or nil."
  461. | char result chunk |
  462. result := '' writeStream.
  463. [char := stream next.
  464. char notNil] whileTrue: [
  465. char = '!!' ifTrue: [
  466. stream peek = '!!'
  467. ifTrue: [stream next "skipping the escape double"]
  468. ifFalse: [^result contents trimBoth "chunk end marker found"]].
  469. result nextPut: char].
  470. ^nil "a chunk needs to end with !!"
  471. ! !
  472. !ChunkParser class methodsFor: 'instance creation'!
  473. on: aStream
  474. ^self new stream: aStream
  475. ! !
  476. Object subclass: #ExportMethodProtocol
  477. instanceVariableNames: 'name theClass'
  478. package: 'Importer-Exporter'!
  479. !ExportMethodProtocol commentStamp!
  480. I am an abstraction for a method protocol in a class / metaclass.
  481. I know of my class, name and methods.
  482. I am used when exporting a package.!
  483. !ExportMethodProtocol methodsFor: 'accessing'!
  484. methods
  485. ^ self theClass methodsInProtocol: self name
  486. !
  487. name
  488. ^name
  489. !
  490. name: aString
  491. name := aString
  492. !
  493. theClass
  494. ^theClass
  495. !
  496. theClass: aClass
  497. theClass := aClass
  498. !
  499. sortedMethods
  500. ^ self methods sorted: [ :a :b | a selector <= b selector ]
  501. ! !
  502. !ExportMethodProtocol class methodsFor: 'instance creation'!
  503. name: aString theClass: aClass
  504. ^self new
  505. name: aString;
  506. theClass: aClass;
  507. yourself
  508. ! !
  509. Object subclass: #Importer
  510. instanceVariableNames: ''
  511. package: 'Importer-Exporter'!
  512. !Importer commentStamp!
  513. I can import Amber code from a string in the chunk format.
  514. ## API
  515. Importer new import: aString!
  516. !Importer methodsFor: 'fileIn'!
  517. import: aStream
  518. | chunk result parser lastEmpty |
  519. parser := ChunkParser on: aStream.
  520. lastEmpty := false.
  521. [chunk := parser nextChunk.
  522. chunk isNil] whileFalse: [
  523. chunk isEmpty
  524. ifTrue: [lastEmpty := true]
  525. ifFalse: [
  526. result := Compiler new evaluateExpression: chunk.
  527. lastEmpty
  528. ifTrue: [
  529. lastEmpty := false.
  530. result scanFrom: parser]]]
  531. ! !
  532. Object subclass: #MethodCategory
  533. instanceVariableNames: 'methods name theClass'
  534. package: 'Importer-Exporter'!
  535. !MethodCategory commentStamp!
  536. I am an abstraction for a method category in a class / metaclass.
  537. I know of my class, name and methods.
  538. I am used when exporting a package.!
  539. !MethodCategory methodsFor: 'accessing'!
  540. methods
  541. ^methods
  542. !
  543. methods: aCollection
  544. methods := aCollection
  545. !
  546. name
  547. ^name
  548. !
  549. name: aString
  550. name := aString
  551. !
  552. theClass
  553. ^theClass
  554. !
  555. theClass: aClass
  556. theClass := aClass
  557. ! !
  558. !MethodCategory class methodsFor: 'not yet classified'!
  559. name: aString theClass: aClass methods: anArray
  560. ^self new
  561. name: aString;
  562. theClass: aClass;
  563. methods: anArray;
  564. yourself
  565. ! !
  566. InterfacingObject subclass: #PackageHandler
  567. instanceVariableNames: ''
  568. package: 'Importer-Exporter'!
  569. !PackageHandler commentStamp!
  570. I am responsible for handling package loading and committing.
  571. I should not be used directly. Instead, use the corresponding `Package` methods.!
  572. !PackageHandler methodsFor: 'accessing'!
  573. commitPathJsFor: aPackage
  574. self subclassResponsibility
  575. !
  576. commitPathStFor: aPackage
  577. self subclassResponsibility
  578. !
  579. exporterClass
  580. ^ Exporter
  581. !
  582. chunkExporterClass
  583. ^ ChunkExporter
  584. !
  585. chunkContentsFor: aPackage
  586. ^ String streamContents: [ :str |
  587. self chunkExporter exportPackage: aPackage on: str ]
  588. !
  589. contentsFor: aPackage
  590. ^ String streamContents: [ :str |
  591. self exporter exportPackage: aPackage on: str ]
  592. ! !
  593. !PackageHandler methodsFor: 'committing'!
  594. commit: aPackage
  595. {
  596. [ self commitStFileFor: aPackage ].
  597. [ self commitJsFileFor: aPackage ]
  598. }
  599. do: [ :each | each value ]
  600. displayingProgress: 'Committing package ', aPackage name
  601. !
  602. oldCommit: aPackage
  603. self commitChannels
  604. do: [ :commitStrategyFactory || fileContents commitStrategy |
  605. commitStrategy := commitStrategyFactory value: aPackage.
  606. fileContents := String streamContents: [ :stream |
  607. (PluggableExporter forRecipe: commitStrategy key) exportPackage: aPackage on: stream ].
  608. self ajaxPutAt: commitStrategy value data: fileContents ]
  609. displayingProgress: 'Committing package ', aPackage name
  610. !
  611. commitStFileFor: aPackage
  612. self
  613. ajaxPutAt: (self commitPathStFor: aPackage), '/', aPackage name, '.st'
  614. data: (self chunkContentsFor: aPackage)
  615. !
  616. commitJsFileFor: aPackage
  617. self
  618. ajaxPutAt: (self commitPathJsFor: aPackage), '/', aPackage name, '.js'
  619. data: (self contentsFor: aPackage)
  620. ! !
  621. !PackageHandler methodsFor: 'factory'!
  622. chunkExporter
  623. ^ self chunkExporterClass default
  624. !
  625. exporter
  626. ^ self exporterClass default
  627. ! !
  628. !PackageHandler methodsFor: 'private'!
  629. ajaxPutAt: aURL data: aString
  630. self
  631. ajax: #{
  632. 'url' -> aURL.
  633. 'type' -> 'PUT'.
  634. 'data' -> aString.
  635. 'contentType' -> 'text/plain;charset=UTF-8'.
  636. 'error' -> [ :xhr | self error: 'Commiting ' , aURL , ' failed with reason: "' , (xhr responseText) , '"'] }
  637. ! !
  638. PackageHandler class instanceVariableNames: 'registry'!
  639. !PackageHandler class methodsFor: 'accessing'!
  640. classRegisteredFor: aString
  641. ^ registry at: aString
  642. !
  643. for: aString
  644. ^ (self classRegisteredFor: aString) new
  645. ! !
  646. !PackageHandler class methodsFor: 'initialization'!
  647. initialize
  648. super initialize.
  649. registry := #{}
  650. ! !
  651. !PackageHandler class methodsFor: 'registry'!
  652. register: aClass for: aString
  653. registry at: aString put: aClass
  654. !
  655. registerFor: aString
  656. PackageHandler register: self for: aString
  657. ! !
  658. PackageHandler subclass: #AmdPackageHandler
  659. instanceVariableNames: ''
  660. package: 'Importer-Exporter'!
  661. !AmdPackageHandler commentStamp!
  662. I am responsible for handling package loading and committing.
  663. I should not be used directly. Instead, use the corresponding `Package` methods.!
  664. !AmdPackageHandler methodsFor: 'accessing'!
  665. commitPathJsFor: aPackage
  666. ^self toUrl: (self namespaceFor: aPackage)
  667. !
  668. commitPathStFor: aPackage
  669. "if _source is not mapped, .st commit will likely fail"
  670. ^self toUrl: (self namespaceFor: aPackage), '/_source'.
  671. !
  672. exporterClass
  673. ^ AmdExporter
  674. ! !
  675. !AmdPackageHandler methodsFor: 'committing'!
  676. namespaceFor: aPackage
  677. ^ aPackage amdNamespace
  678. ifNil: [ aPackage amdNamespace: self class defaultNamespace; amdNamespace ]
  679. ! !
  680. !AmdPackageHandler methodsFor: 'private'!
  681. toUrl: aString
  682. ^ Smalltalk current amdRequire
  683. ifNil: [ self error: 'AMD loader not present' ]
  684. ifNotNil: [ :require | (require basicAt: 'toUrl') value: aString ]
  685. ! !
  686. !AmdPackageHandler class methodsFor: 'commit paths'!
  687. defaultNamespace
  688. ^ Smalltalk current defaultAMDNamespace
  689. !
  690. defaultNamespace: aString
  691. Smalltalk current defaultAMDNamespace: aString
  692. !
  693. resetCommitPaths
  694. defaultNamespace := nil
  695. ! !
  696. !AmdPackageHandler class methodsFor: 'initialization'!
  697. initialize
  698. super initialize.
  699. self registerFor: 'amd'
  700. ! !
  701. PackageHandler subclass: #LegacyPackageHandler
  702. instanceVariableNames: ''
  703. package: 'Importer-Exporter'!
  704. !LegacyPackageHandler commentStamp!
  705. I am responsible for handling package loading and committing.
  706. I should not be used directly. Instead, use the corresponding `Package` methods.!
  707. !LegacyPackageHandler methodsFor: 'committing'!
  708. commitChannels
  709. ^{
  710. [ :pkg | Exporter default recipe -> (pkg commitPathJs, '/', pkg name, '.js') ].
  711. [ :pkg | StrippedExporter default recipe -> (pkg commitPathJs, '/', pkg name, '.deploy.js') ].
  712. [ :pkg | ChunkExporter default recipe -> (pkg commitPathSt, '/', pkg name, '.st') ]
  713. }
  714. !
  715. commitPathJsFor: aPackage
  716. ^self class defaultCommitPathJs
  717. !
  718. commitPathStFor: aPackage
  719. ^self class defaultCommitPathSt
  720. ! !
  721. !LegacyPackageHandler methodsFor: 'loading'!
  722. loadPackage: packageName prefix: aString
  723. | url |
  724. url := '/', aString, '/js/', packageName, '.js'.
  725. self
  726. ajax: #{
  727. 'url' -> url.
  728. 'type' -> 'GET'.
  729. 'dataType' -> 'script'.
  730. 'complete' -> [ :jqXHR :textStatus |
  731. jqXHR readyState = 4
  732. ifTrue: [ self setupPackageNamed: packageName prefix: aString ] ].
  733. 'error' -> [ self alert: 'Could not load package at: ', url ]
  734. }
  735. !
  736. loadPackages: aCollection prefix: aString
  737. aCollection do: [ :each |
  738. self loadPackage: each prefix: aString ]
  739. ! !
  740. !LegacyPackageHandler methodsFor: 'private'!
  741. setupPackageNamed: packageName prefix: aString
  742. (Package named: packageName)
  743. setupClasses;
  744. commitPathJs: '/', aString, '/js';
  745. commitPathSt: '/', aString, '/st'
  746. ! !
  747. LegacyPackageHandler class instanceVariableNames: 'defaultCommitPathJs defaultCommitPathSt'!
  748. !LegacyPackageHandler class methodsFor: 'commit paths'!
  749. commitPathsFromLoader
  750. <
  751. var commitPath = typeof amber !!== 'undefined' && amber.commitPath;
  752. if (!!commitPath) return;
  753. if (commitPath.js) self._defaultCommitPathJs_(commitPath.js);
  754. if (commitPath.st) self._defaultCommitPathSt_(commitPath.st);
  755. >
  756. !
  757. defaultCommitPathJs
  758. ^ defaultCommitPathJs ifNil: [ defaultCommitPathJs := 'js']
  759. !
  760. defaultCommitPathJs: aString
  761. defaultCommitPathJs := aString
  762. !
  763. defaultCommitPathSt
  764. ^ defaultCommitPathSt ifNil: [ defaultCommitPathSt := 'st']
  765. !
  766. defaultCommitPathSt: aString
  767. defaultCommitPathSt := aString
  768. !
  769. resetCommitPaths
  770. defaultCommitPathJs := nil.
  771. defaultCommitPathSt := nil
  772. ! !
  773. !LegacyPackageHandler class methodsFor: 'initialization'!
  774. initialize
  775. super initialize.
  776. self registerFor: 'unknown'.
  777. self commitPathsFromLoader
  778. ! !
  779. !LegacyPackageHandler class methodsFor: 'loading'!
  780. loadPackages: aCollection prefix: aString
  781. ^ self new loadPackages: aCollection prefix: aString
  782. ! !
  783. Object subclass: #PluggableExporter
  784. instanceVariableNames: 'recipe'
  785. package: 'Importer-Exporter'!
  786. !PluggableExporter commentStamp!
  787. I am an engine for exporting structured data on a Stream.
  788. My instances are created using
  789. PluggableExporter forRecipe: aRecipe,
  790. where recipe is structured description of the exporting algorithm (see `ExportRecipeInterpreter`).
  791. The actual exporting is done by interpreting the recipe using a `RecipeInterpreter`.
  792. I am used to export amber packages, so I have a convenience method
  793. `exportPackage: aPackage on: aStream`
  794. which exports `aPackage` using the `recipe`
  795. (it is otherwise no special, so it may be renamed to export:on:)!
  796. !PluggableExporter methodsFor: 'accessing'!
  797. interpreter
  798. ^ ExportRecipeInterpreter new
  799. !
  800. recipe
  801. ^recipe
  802. !
  803. recipe: anArray
  804. recipe := anArray
  805. ! !
  806. !PluggableExporter methodsFor: 'fileOut'!
  807. exportAllPackages
  808. "Export all packages in the system."
  809. ^String streamContents: [:stream |
  810. Smalltalk current packages do: [:pkg |
  811. self exportPackage: pkg on: stream]]
  812. !
  813. exportPackage: aPackage on: aStream
  814. self interpreter interpret: self recipe for: aPackage on: aStream
  815. ! !
  816. !PluggableExporter class methodsFor: 'convenience'!
  817. ownClassesOfPackage: package
  818. "Export classes in dependency order.
  819. Update (issue #171): Remove duplicates for export"
  820. ^package sortedClasses asSet
  821. ! !
  822. !PluggableExporter class methodsFor: 'instance creation'!
  823. forRecipe: aRecipe
  824. ^self new recipe: aRecipe; yourself
  825. ! !
  826. !Package methodsFor: '*Importer-Exporter'!
  827. amdNamespace
  828. <return (self.transport && self.transport.amdNamespace) || nil>
  829. !
  830. amdNamespace: aString
  831. <
  832. if (!!self.transport) { self.transport = { type: 'amd' }; }
  833. if (self.transport.type !!== 'amd') { throw new Error('Package '+self._name()+' has transport type '+self.transport.type+', not "amd".'); }
  834. self.transport.amdNamespace = aString;
  835. >
  836. !
  837. commit
  838. ^ self handler commit: self
  839. !
  840. commitPathJs
  841. ^ (extension ifNil: [ extension := #{} ]) at: #commitPathJs ifAbsent: [ self handler commitPathJsFor: self ]
  842. !
  843. commitPathJs: aString
  844. ^ (extension ifNil: [ extension := #{} ]) at: #commitPathJs put: aString
  845. !
  846. commitPathSt
  847. ^ (extension ifNil: [ extension := #{} ]) at: #commitPathSt ifAbsent: [ self handler commitPathStFor: self ]
  848. !
  849. commitPathSt: aString
  850. ^ (extension ifNil: [ extension := #{} ]) at: #commitPathSt put: aString
  851. !
  852. handler
  853. ^ PackageHandler for: self transportType
  854. !
  855. transportJson
  856. <return JSON.stringify(self.transport || null);>
  857. !
  858. transportType
  859. <return (self.transport && self.transport.type) || 'unknown';>
  860. ! !