Importer-Exporter.st 29 KB

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