Importer-Exporter.st 30 KB

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