| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835 | Smalltalk current createPackage: 'Kernel-ImportExport'!Object subclass: #AbstractExporter	instanceVariableNames: ''	package: 'Kernel-ImportExport'!!AbstractExporter commentStamp!I am an abstract exporter for Amber source code.## APIUse `#exportPackage:on:` to export a given package on a Stream.!!AbstractExporter methodsFor: 'accessing'!extensionMethodsOfPackage: aPackage	| result |		result := OrderedCollection new.		(self extensionProtocolsOfPackage: aPackage) do: [ :each |		result addAll: each methods ].			^ result!extensionProtocolsOfPackage: aPackage	| extensionName result |		extensionName := '*', aPackage name.	result := OrderedCollection new.		"The classes must be loaded since it is extensions only.	Therefore sorting (dependency resolution) does not matter here.	Not sorting improves the speed by a number of magnitude."		Smalltalk current classes do: [ :each |		{each. each class} do: [ :behavior |			(behavior protocols includes: extensionName) ifTrue: [				result add: (ExportMethodProtocol name: extensionName theClass: behavior) ] ] ].	^ result! !!AbstractExporter methodsFor: 'convenience'!chunkEscape: aString	"Replace all occurrences of !! with !!!! and trim at both ends."	^ (aString replace: '!!' with: '!!!!') trimBoth!classNameFor: aClass	^ aClass isMetaclass		ifTrue: [ aClass instanceClass name, ' class' ]		ifFalse: [			aClass isNil				ifTrue: [ 'nil' ]				ifFalse: [ aClass name ] ]! !!AbstractExporter methodsFor: 'output'!exportPackage: aPackage on: aStream	self subclassResponsibility! !AbstractExporter subclass: #ChunkExporter	instanceVariableNames: ''	package: 'Kernel-ImportExport'!!ChunkExporter commentStamp!I am an exporter dedicated to outputting Amber source code in the classic Smalltalk chunk format.I do not output any compiled code.!!ChunkExporter methodsFor: 'accessing'!extensionCategoriesOfPackage: aPackage	"Issue #143: sort protocol alphabetically"	| name map result |	name := aPackage name.	result := OrderedCollection new.	(Package sortedClasses: Smalltalk current classes) do: [ :each |		{each. each class} do: [ :aClass |			map := Dictionary new.			aClass protocolsDo: [ :category :methods |				category = ('*', name) ifTrue: [ map at: category put: methods ] ].			result addAll: ((map keys sorted: [ :a :b | a <= b ]) collect: [ :category |				MethodCategory name: category theClass: aClass methods: (map at: category) ]) ] ].	^ result!ownCategoriesOfClass: aClass	"Answer the protocols of aClass that are not package extensions"		"Issue #143: sort protocol alphabetically"	| map |	map := Dictionary new.	aClass protocolsDo: [ :each :methods |		(each match: '^\*') ifFalse: [ map at: each put: methods ] ].	^ (map keys sorted: [ :a :b | a <= b ]) collect: [ :each |		MethodCategory name: each theClass: aClass methods: (map at: each) ]!ownCategoriesOfMetaClass: aClass	"Issue #143: sort protocol alphabetically"	^ self ownCategoriesOfClass: aClass class!ownMethodProtocolsOfClass: aClass	"Answer a collection of ExportMethodProtocol object of aClass that are not package extensions"		^ aClass ownProtocols collect: [ :each |		ExportMethodProtocol name: each theClass: aClass ]! !!ChunkExporter methodsFor: 'output'!exportCategoryEpilogueOf: aCategory on: aStream	aStream nextPutAll: ' !!'; lf; lf!exportCategoryPrologueOf: aCategory on: aStream	aStream		nextPutAll: '!!', (self classNameFor: aCategory theClass);		nextPutAll: ' methodsFor: ''', aCategory name, '''!!'!exportDefinitionOf: aClass on: aStream	"Chunk format."	aStream		nextPutAll: (self classNameFor: aClass superclass);		nextPutAll: ' subclass: #', (self classNameFor: aClass); lf;		tab; nextPutAll: 'instanceVariableNames: '''.	aClass instanceVariableNames		do: [ :each | aStream nextPutAll: each ]		separatedBy: [ aStream nextPutAll: ' ' ].	aStream		nextPutAll: ''''; lf;		tab; nextPutAll: 'package: ''', aClass category, '''!!'; lf.	aClass comment notEmpty ifTrue: [		aStream		nextPutAll: '!!', (self classNameFor: aClass), ' commentStamp!!';lf;		nextPutAll: (self chunkEscape: aClass comment), '!!';lf ].	aStream lf!exportMetaDefinitionOf: aClass on: aStream	aClass class instanceVariableNames isEmpty ifFalse: [		aStream			nextPutAll: (self classNameFor: aClass class);			nextPutAll: ' instanceVariableNames: '''.		aClass class instanceVariableNames			do: [ :each | aStream nextPutAll: each ]			separatedBy: [ aStream nextPutAll: ' ' ].		aStream			nextPutAll: '''!!'; lf; lf ]!exportMethod: aMethod on: aStream	aStream		lf; lf; nextPutAll: (self chunkEscape: aMethod source); lf;		nextPutAll: '!!'!exportPackage: aPackage on: aStream	self exportPackageDefinitionOf: aPackage on: aStream.		aPackage sortedClasses do: [ :each |		self exportDefinitionOf: each on: aStream.				self 			exportProtocols: (self ownMethodProtocolsOfClass: each)			on: aStream.					self exportMetaDefinitionOf: each on: aStream.				self 			exportProtocols: (self ownMethodProtocolsOfClass: each class)			on: aStream ].				self 		exportProtocols: (self extensionProtocolsOfPackage: aPackage)		on: aStream!exportPackageDefinitionOf: aPackage on: aStream	aStream		nextPutAll: 'Smalltalk current createPackage: ''', aPackage name, '''!!';		lf!exportProtocol: aProtocol on: aStream	self exportProtocolPrologueOf: aProtocol on: aStream.	aProtocol methods do: [ :method | 		self exportMethod: method on: aStream ].	self exportProtocolEpilogueOf: aProtocol on: aStream!exportProtocolEpilogueOf: aProtocol on: aStream	aStream nextPutAll: ' !!'; lf; lf!exportProtocolPrologueOf: aProtocol on: aStream	aStream		nextPutAll: '!!', (self classNameFor: aProtocol theClass);		nextPutAll: ' methodsFor: ''', aProtocol name, '''!!'!exportProtocols: aCollection on: aStream	aCollection do: [ :each |		self exportProtocol: each on: aStream ]! !AbstractExporter subclass: #Exporter	instanceVariableNames: ''	package: 'Kernel-ImportExport'!!Exporter commentStamp!I am responsible for outputting Amber code into a JavaScript string.The generated output is enough to reconstruct the exported data, including Smalltalk source code and other metadata.## Use caseI am typically used to save code outside of the Amber runtime (committing to disk, etc.).!!Exporter methodsFor: 'accessing'!ownMethodsOfClass: aClass	"Issue #143: sort methods alphabetically"	^ ((aClass methodDictionary values) sorted: [ :a :b | a selector <= b selector ])		reject: [ :each | (each category match: '^\*') ]!ownMethodsOfMetaClass: aClass	"Issue #143: sort methods alphabetically"	^ self ownMethodsOfClass: aClass class! !!Exporter methodsFor: 'convenience'!classNameFor: aClass	^ aClass isMetaclass		ifTrue: [ aClass instanceClass name, '.klass' ]		ifFalse: [			aClass isNil				ifTrue: [ 'nil' ]				ifFalse: [ aClass name ] ]! !!Exporter methodsFor: 'output'!exportDefinitionOf: aClass on: aStream	aStream		lf;		nextPutAll: 'smalltalk.addClass(';		nextPutAll: '''', (self classNameFor: aClass), ''', ';		nextPutAll: 'smalltalk.', (self classNameFor: aClass superclass);		nextPutAll: ', ['.	aClass instanceVariableNames		do: [ :each | aStream nextPutAll: '''', each, '''' ]		separatedBy: [ aStream nextPutAll: ', ' ].	aStream		nextPutAll: '], ''';		nextPutAll: aClass category, '''';		nextPutAll: ');'.	aClass comment notEmpty ifTrue: [		aStream			lf;		nextPutAll: 'smalltalk.';		nextPutAll: (self classNameFor: aClass);		nextPutAll: '.comment=';		nextPutAll: aClass comment asJavascript;		nextPutAll: ';' ].	aStream lf!exportMetaDefinitionOf: aClass on: aStream	aStream lf.	aClass class instanceVariableNames isEmpty ifFalse: [		aStream		nextPutAll: 'smalltalk.', (self classNameFor: aClass class);		nextPutAll: '.iVarNames = ['.		aClass class instanceVariableNames		do: [ :each | aStream nextPutAll: '''', each, '''' ]		separatedBy: [ aStream nextPutAll: ',' ].		aStream nextPutAll: '];', String lf ]!exportMethod: aMethod on: aStream	aStream		nextPutAll: 'smalltalk.addMethod(';lf;		"nextPutAll: aMethod selector asSelector asJavascript, ',';lf;"		nextPutAll: 'smalltalk.method({';lf;		nextPutAll: 'selector: ', aMethod selector asJavascript, ',';lf;		nextPutAll: 'category: ''', aMethod category, ''',';lf;		nextPutAll: 'fn: ', aMethod fn compiledSource, ',';lf;		nextPutAll: 'args: ', aMethod arguments asJavascript, ','; lf;		nextPutAll: 'source: ', aMethod source asJavascript, ',';lf;		nextPutAll: 'messageSends: ', aMethod messageSends asJavascript, ',';lf;		nextPutAll: 'referencedClasses: ', aMethod referencedClasses asJavascript.	aStream		lf;		nextPutAll: '}),';lf;		nextPutAll: 'smalltalk.', (self classNameFor: aMethod methodClass);		nextPutAll: ');';lf;lf!exportPackage: aPackage on: aStream		self 		exportPackagePrologueOf: aPackage on: aStream;		exportPackageDefinitionOf: aPackage on: aStream;		exportPackageTransportOf: aPackage on: aStream.		aPackage sortedClasses do: [ :each |		self exportDefinitionOf: each on: aStream.		each ownMethods do: [ :method |			self exportMethod: method on: aStream ].					self exportMetaDefinitionOf: each on: aStream.		each class ownMethods do: [ :method |			self exportMethod: method on: aStream ] ].				(self extensionMethodsOfPackage: aPackage) do: [ :each |		self exportMethod: each on: aStream ].			self exportPackageEpilogueOf: aPackage on: aStream!exportPackageDefinitionOf: aPackage on: aStream	aStream		nextPutAll: 'smalltalk.addPackage(';		nextPutAll: '''', aPackage name, ''');';		lf!exportPackageEpilogueOf: aPackage on: aStream	aStream		nextPutAll: '})(global_smalltalk,global_nil,global__st);';		lf!exportPackagePrologueOf: aPackage on: aStream	aStream		nextPutAll: '(function(smalltalk,nil,_st){';		lf!exportPackageTransportOf: aPackage on: aStream	aStream		nextPutAll: 'smalltalk.packages[';		nextPutAll: aPackage name asJavascript;		nextPutAll: '].transport = ';		nextPutAll: aPackage transport asJSONString;		nextPutAll: ';';		lf! !Exporter subclass: #AmdExporter	instanceVariableNames: 'namespace'	package: 'Kernel-ImportExport'!!AmdExporter commentStamp!I am used to export Packages in an AMD (Asynchronous Module Definition) JavaScript format.!!AmdExporter methodsFor: 'output'!exportPackageEpilogueOf: aPackage on: aStream	aStream		nextPutAll: '});';		lf!exportPackagePrologueOf: aPackage on: aStream	aStream		nextPutAll: 'define("';		nextPutAll: (self amdNamespaceOfPackage: aPackage);		nextPutAll: '/';		nextPutAll: aPackage name;		nextPutAll: '", ';		nextPutAll: (#('amber_vm/smalltalk' 'amber_vm/nil' 'amber_vm/_st'), (self amdNamesOfPackages: aPackage loadDependencies)) asJavascript;		nextPutAll: ', function(smalltalk,nil,_st){';		lf! !!AmdExporter methodsFor: 'private'!amdNamesOfPackages: anArray	^ (anArray		select: [ :each | (self amdNamespaceOfPackage: each) notNil ])		collect: [ :each | (self amdNamespaceOfPackage: each), '/', each name ]!amdNamespaceOfPackage: aPackage	^ (aPackage transport type = 'amd')		ifTrue: [ aPackage transport namespace ]		ifFalse: [ nil ]! !Object subclass: #ChunkParser	instanceVariableNames: 'stream'	package: 'Kernel-ImportExport'!!ChunkParser commentStamp!I am responsible for parsing aStream contents in the chunk format.## API    ChunkParser new        stream: aStream;        nextChunk!!ChunkParser methodsFor: 'accessing'!stream: aStream	stream := aStream! !!ChunkParser methodsFor: 'reading'!nextChunk	"The chunk format (Smalltalk Interchange Format or Fileout format)	is a trivial format but can be a bit tricky to understand:		- Uses the exclamation mark as delimiter of chunks.		- Inside a chunk a normal exclamation mark must be doubled.		- A non empty chunk must be a valid Smalltalk expression.		- A chunk on top level with a preceding empty chunk is an instruction chunk:			- The object created by the expression then takes over reading chunks.	This method returns next chunk as a String (trimmed), empty String (all whitespace) or nil."	| char result chunk |	result := '' writeStream.		[ char := stream next.		char notNil ] whileTrue: [				char = '!!' ifTrue: [						stream peek = '!!'								ifTrue: [ stream next "skipping the escape double" ]								ifFalse: [ ^ result contents trimBoth "chunk end marker found" ]].				result nextPut: char ].	^ nil "a chunk needs to end with !!"! !!ChunkParser class methodsFor: 'instance creation'!on: aStream	^ self new stream: aStream! !Object subclass: #ExportMethodProtocol	instanceVariableNames: 'name theClass'	package: 'Kernel-ImportExport'!!ExportMethodProtocol commentStamp!I am an abstraction for a method protocol in a class / metaclass.I know of my class, name and methods.I am used when exporting a package.!!ExportMethodProtocol methodsFor: 'accessing'!methods	^ (self theClass methodsInProtocol: self name)		sorted: [ :a :b | a selector <= b selector ]!name	^ name!name: aString	name := aString!theClass	^ theClass!theClass: aClass	theClass := aClass! !!ExportMethodProtocol class methodsFor: 'instance creation'!name: aString theClass: aClass	^ self new		name: aString;		theClass: aClass;		yourself! !Object subclass: #Importer	instanceVariableNames: ''	package: 'Kernel-ImportExport'!!Importer commentStamp!I can import Amber code from a string in the chunk format.## API    Importer new import: aString!!Importer methodsFor: 'fileIn'!import: aStream	| chunk result parser lastEmpty |	parser := ChunkParser on: aStream.	lastEmpty := false.	[ chunk := parser nextChunk.	chunk isNil ] whileFalse: [		chunk isEmpty			ifTrue: [ lastEmpty := true ]			ifFalse: [				result := Compiler new evaluateExpression: chunk.				lastEmpty						ifTrue: [									lastEmpty := false.									result scanFrom: parser ]] ]! !InterfacingObject subclass: #PackageHandler	instanceVariableNames: ''	package: 'Kernel-ImportExport'!!PackageHandler commentStamp!I am responsible for handling package loading and committing.I should not be used directly. Instead, use the corresponding `Package` methods.!!PackageHandler methodsFor: 'accessing'!chunkContentsFor: aPackage	^ String streamContents: [ :str |		self chunkExporter exportPackage: aPackage on: str ]!chunkExporterClass	^ ChunkExporter!commitPathJsFor: aPackage	self subclassResponsibility!commitPathStFor: aPackage	self subclassResponsibility!contentsFor: aPackage	^ String streamContents: [ :str |		self exporter exportPackage: aPackage on: str ]!exporterClass	^ Exporter! !!PackageHandler methodsFor: 'committing'!commit: aPackage	{		[ self commitStFileFor: aPackage ].		[ self commitJsFileFor: aPackage ]	}		do: [ :each | each value ]		displayingProgress: 'Committing package ', aPackage name!commitJsFileFor: aPackage	self 		ajaxPutAt: (self commitPathJsFor: aPackage), '/', aPackage name, '.js'		data: (self contentsFor: aPackage)!commitStFileFor: aPackage	self 		ajaxPutAt: (self commitPathStFor: aPackage), '/', aPackage name, '.st'		data: (self chunkContentsFor: aPackage)! !!PackageHandler methodsFor: 'factory'!chunkExporter	^ self chunkExporterClass new!exporter	^ self exporterClass new! !!PackageHandler methodsFor: 'private'!ajaxPutAt: aURL data: aString	self		ajax: #{			'url' -> aURL.			'type' -> 'PUT'.			'data' -> aString.			'contentType' -> 'text/plain;charset=UTF-8'.			'error' -> [ :xhr | self alert: 'Commiting ' , aURL , ' failed with reason: "' , (xhr responseText) , '"' ] }! !PackageHandler subclass: #AmdPackageHandler	instanceVariableNames: ''	package: 'Kernel-ImportExport'!!AmdPackageHandler commentStamp!I am responsible for handling package loading and committing.I should not be used directly. Instead, use the corresponding `Package` methods.!!AmdPackageHandler methodsFor: 'accessing'!commitPathJsFor: aPackage	^ self toUrl: (self namespaceFor: aPackage)!commitPathStFor: aPackage	"if _source is not mapped, .st commit will likely fail"	^ self toUrl: (self namespaceFor: aPackage), '/_source'.!exporterClass	^ AmdExporter! !!AmdPackageHandler methodsFor: 'committing'!namespaceFor: aPackage	^ aPackage transport namespace! !!AmdPackageHandler methodsFor: 'private'!toUrl: aString	^ Smalltalk current amdRequire		ifNil: [ self error: 'AMD loader not present' ]		ifNotNil: [ :require | (require basicAt: 'toUrl') value: aString ]! !!AmdPackageHandler class methodsFor: 'commit paths'!defaultNamespace	^ Smalltalk current defaultAmdNamespace!defaultNamespace: aString	Smalltalk current defaultAmdNamespace: aString! !Object subclass: #PackageTransport	instanceVariableNames: 'package'	package: 'Kernel-ImportExport'!!PackageTransport commentStamp!I represent the transport mechanism used to commit a package.My concrete subclasses have a `#handler` to which committing is delegated.!!PackageTransport methodsFor: 'accessing'!commitHandlerClass	self subclassResponsibility!definition	^ ''!package	^ package!package: aPackage	package := aPackage!type	^ self class type! !!PackageTransport methodsFor: 'committing'!commit	self commitHandler commit: self package! !!PackageTransport methodsFor: 'converting'!asJSON	^ #{ 'type' -> self type }! !!PackageTransport methodsFor: 'factory'!commitHandler	^ self commitHandlerClass new! !!PackageTransport methodsFor: 'initialization'!setupFromJson: anObject	"no op. override if needed in subclasses"! !PackageTransport class instanceVariableNames: 'registry'!!PackageTransport class methodsFor: 'accessing'!classRegisteredFor: aString	^ registry at: aString!defaultType	^ AmdPackageTransport type!type	"Override in subclasses"	^ nil! !!PackageTransport class methodsFor: 'initialization'!initialize	super initialize.	registry := #{}.	self register! !!PackageTransport class methodsFor: 'instance creation'!for: aString	^ (self classRegisteredFor: aString) new!fromJson: anObject	anObject ifNil: [ ^ self for: self defaultType ].		^ (self for: anObject type)		setupFromJson: anObject;		yourself! !!PackageTransport class methodsFor: 'registration'!register	PackageTransport register: self!register: aClass	aClass type ifNotNil: [		registry at: aClass type put: aClass ]! !PackageTransport subclass: #AmdPackageTransport	instanceVariableNames: 'namespace'	package: 'Kernel-ImportExport'!!AmdPackageTransport commentStamp!I am the default transport for committing packages.See `AmdExporter` and `AmdPackageHandler`.!!AmdPackageTransport methodsFor: 'accessing'!commitHandlerClass	^ AmdPackageHandler!definition	^ String streamContents: [ :stream |		stream 			nextPutAll: self class name;			nextPutAll: ' namespace: ';			nextPutAll: '''', self namespace, '''' ]!namespace	^ namespace ifNil: [ self defaultNamespace ]!namespace: aString	namespace := aString! !!AmdPackageTransport methodsFor: 'converting'!asJSON	^ super asJSON		at: 'amdNamespace' put: self namespace;		yourself! !!AmdPackageTransport methodsFor: 'defaults'!defaultNamespace	^ Smalltalk current defaultAmdNamespace! !!AmdPackageTransport methodsFor: 'initialization'!setupFromJson: anObject	self namespace: (anObject at: 'amdNamespace')! !!AmdPackageTransport methodsFor: 'printing'!printOn: aStream	super printOn: aStream.	aStream		nextPutAll: ' (AMD Namespace: ';		nextPutAll: self namespace;		nextPutAll: ')'! !!AmdPackageTransport class methodsFor: 'accessing'!type	^ 'amd'! !!AmdPackageTransport class methodsFor: 'instance creation'!namespace: aString	^ self new		namespace: aString;		yourself! !!Package methodsFor: '*Kernel-ImportExport'!commit	^ self transport commit! !
 |