Platform-ImportExport.st 29 KB

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