Platform-ImportExport.st 29 KB

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