Importer-Exporter.st 33 KB

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