|  | @@ -1,4 +1,177 @@
 | 
	
		
			
				|  |  |  Smalltalk current createPackage: 'Importer-Exporter'!
 | 
	
		
			
				|  |  | +Object subclass: #AmdExporter
 | 
	
		
			
				|  |  | +	instanceVariableNames: ''
 | 
	
		
			
				|  |  | +	package: 'Importer-Exporter'!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +!AmdExporter class methodsFor: 'exporting-output'!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +exportPackageEpilogueOf: aPackage on: aStream
 | 
	
		
			
				|  |  | +	aStream
 | 
	
		
			
				|  |  | +		nextPutAll: '});';
 | 
	
		
			
				|  |  | +		lf
 | 
	
		
			
				|  |  | +!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +exportPackagePrologueOf: aPackage on: aStream
 | 
	
		
			
				|  |  | +	aStream
 | 
	
		
			
				|  |  | +		nextPutAll: 'define("amber/';
 | 
	
		
			
				|  |  | +		nextPutAll: aPackage name;
 | 
	
		
			
				|  |  | +		nextPutAll: '", ["amber_vm/smalltalk","amber_vm/nil","amber_vm/_st"], function(smalltalk,nil,_st){';
 | 
	
		
			
				|  |  | +		lf
 | 
	
		
			
				|  |  | +! !
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +Object subclass: #ChunkExporter
 | 
	
		
			
				|  |  | +	instanceVariableNames: ''
 | 
	
		
			
				|  |  | +	package: 'Importer-Exporter'!
 | 
	
		
			
				|  |  | +!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 class methodsFor: 'exporting-accessing'!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +extensionCategoriesOfPackage: package
 | 
	
		
			
				|  |  | +	"Issue #143: sort protocol alphabetically"
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	| name map result |
 | 
	
		
			
				|  |  | +	name := package name.
 | 
	
		
			
				|  |  | +	result := OrderedCollection new.
 | 
	
		
			
				|  |  | +	(Package sortedClasses: Smalltalk current classes) do: [:each |
 | 
	
		
			
				|  |  | +		{each. each class} do: [:aClass |
 | 
	
		
			
				|  |  | +			map := Dictionary new.
 | 
	
		
			
				|  |  | +			aClass protocolsDo: [:category :methods |
 | 
	
		
			
				|  |  | +				(category match: '^\*', name) ifTrue: [ map at: category put: methods ]].
 | 
	
		
			
				|  |  | +			result addAll: ((map keys sorted: [:a :b | a <= b ]) collect: [:category |
 | 
	
		
			
				|  |  | +				#{ 'methods'->(map at: category). 'name'->category. 'class'->aClass}]) ]].
 | 
	
		
			
				|  |  | +	^result
 | 
	
		
			
				|  |  | +!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +methodsOfCategory: category
 | 
	
		
			
				|  |  | +	"Issue #143: sort methods alphabetically"
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	^(category at: #methods) sorted: [:a :b | a selector <= b selector]
 | 
	
		
			
				|  |  | +!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +ownCategoriesOfClass: aClass
 | 
	
		
			
				|  |  | +	"Issue #143: sort protocol alphabetically"
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	| map |
 | 
	
		
			
				|  |  | +	map := Dictionary new.
 | 
	
		
			
				|  |  | +	aClass protocolsDo: [:category :methods |
 | 
	
		
			
				|  |  | +		(category match: '^\*') ifFalse: [ map at: category put: methods ]].
 | 
	
		
			
				|  |  | +	^(map keys sorted: [:a :b | a <= b ]) collect: [:category |
 | 
	
		
			
				|  |  | +		#{
 | 
	
		
			
				|  |  | +			'methods'->(map at: category).
 | 
	
		
			
				|  |  | +			'name'->category.
 | 
	
		
			
				|  |  | +			'class'->aClass }]
 | 
	
		
			
				|  |  | +!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +ownCategoriesOfMetaClass: aClass
 | 
	
		
			
				|  |  | +	"Issue #143: sort protocol alphabetically"
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	^self ownCategoriesOfClass: aClass class
 | 
	
		
			
				|  |  | +! !
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +!ChunkExporter class methodsFor: 'exporting-output'!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +exportCategoryEpilogueOf: category on: aStream
 | 
	
		
			
				|  |  | +	aStream nextPutAll: ' !!'; lf; lf
 | 
	
		
			
				|  |  | +!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +exportCategoryPrologueOf: category on: aStream
 | 
	
		
			
				|  |  | +	aStream
 | 
	
		
			
				|  |  | +		nextPutAll: '!!', (self classNameFor: (category at: #class));
 | 
	
		
			
				|  |  | +		nextPutAll: ' methodsFor: ''', (category at: #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: '!!'
 | 
	
		
			
				|  |  | +!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +exportPackageDefinitionOf: package on: aStream
 | 
	
		
			
				|  |  | +	"Chunk format."
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	aStream
 | 
	
		
			
				|  |  | +		nextPutAll: 'Smalltalk current createPackage: ''', package name, '''!!';
 | 
	
		
			
				|  |  | +		lf
 | 
	
		
			
				|  |  | +! !
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +!ChunkExporter class methodsFor: 'fileOut'!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +recipe
 | 
	
		
			
				|  |  | +	"Export a given package."
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	| exportCategoryRecipe |
 | 
	
		
			
				|  |  | +	exportCategoryRecipe := {
 | 
	
		
			
				|  |  | +		self -> #exportCategoryPrologueOf:on:.
 | 
	
		
			
				|  |  | +		{
 | 
	
		
			
				|  |  | +			self -> #methodsOfCategory:.
 | 
	
		
			
				|  |  | +			self -> #exportMethod:on: }.
 | 
	
		
			
				|  |  | +		self -> #exportCategoryEpilogueOf:on: }.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	^{
 | 
	
		
			
				|  |  | +		self -> #exportPackageDefinitionOf:on:.
 | 
	
		
			
				|  |  | +		{
 | 
	
		
			
				|  |  | +			PluggableExporter -> #ownClassesOfPackage:.
 | 
	
		
			
				|  |  | +			self -> #exportDefinitionOf:on:.
 | 
	
		
			
				|  |  | +			{ self -> #ownCategoriesOfClass: }, exportCategoryRecipe.
 | 
	
		
			
				|  |  | +			self -> #exportMetaDefinitionOf:on:.
 | 
	
		
			
				|  |  | +			{ self -> #ownCategoriesOfMetaClass: }, exportCategoryRecipe }.
 | 
	
		
			
				|  |  | +		{ self -> #extensionCategoriesOfPackage: }, exportCategoryRecipe
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +! !
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +!ChunkExporter class methodsFor: 'private'!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +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]]
 | 
	
		
			
				|  |  | +! !
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  Object subclass: #ChunkParser
 | 
	
		
			
				|  |  |  	instanceVariableNames: 'stream'
 | 
	
		
			
				|  |  |  	package: 'Importer-Exporter'!
 | 
	
	
		
			
				|  | @@ -64,59 +237,40 @@ I am typically used to save code outside of the Amber runtime (committing to dis
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  Use `#exportAll`, `#exportClass:` or `#exportPackage:` methods.!
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -!Exporter methodsFor: 'fileOut'!
 | 
	
		
			
				|  |  | +!Exporter class methodsFor: 'exporting-accessing'!
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -exportAll
 | 
	
		
			
				|  |  | -	"Export all packages in the system."
 | 
	
		
			
				|  |  | +extensionMethodsOfPackage: package
 | 
	
		
			
				|  |  | +	"Issue #143: sort classes and methods alphabetically"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	^String streamContents: [:stream |
 | 
	
		
			
				|  |  | -		Smalltalk current packages do: [:pkg |
 | 
	
		
			
				|  |  | -		stream nextPutAll: (self exportPackage: pkg name)]]
 | 
	
		
			
				|  |  | +	| name result |
 | 
	
		
			
				|  |  | +	name := package name.
 | 
	
		
			
				|  |  | +	result := OrderedCollection new.
 | 
	
		
			
				|  |  | +	(Package sortedClasses: Smalltalk current classes) do: [:each |
 | 
	
		
			
				|  |  | +		{each. each class} do: [:aClass |
 | 
	
		
			
				|  |  | +			result addAll: (((aClass methodDictionary values)
 | 
	
		
			
				|  |  | +				sorted: [:a :b | a selector <= b selector])
 | 
	
		
			
				|  |  | +				select: [:method | method category match: '^\*', name]) ]].
 | 
	
		
			
				|  |  | +	^result
 | 
	
		
			
				|  |  |  !
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -exportClass: aClass
 | 
	
		
			
				|  |  | -	"Export a single class. Subclasses override these methods."
 | 
	
		
			
				|  |  | +ownMethodsOfClass: aClass
 | 
	
		
			
				|  |  | +	"Issue #143: sort methods alphabetically"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	^String streamContents: [:stream |
 | 
	
		
			
				|  |  | -		self exportDefinitionOf: aClass on: stream.
 | 
	
		
			
				|  |  | -		self exportMethodsOf: aClass on: stream.
 | 
	
		
			
				|  |  | -		self exportMetaDefinitionOf: aClass on: stream.
 | 
	
		
			
				|  |  | -		self exportMethodsOf: aClass class on: stream]
 | 
	
		
			
				|  |  | +	^((aClass methodDictionary values) sorted: [:a :b | a selector <= b selector])
 | 
	
		
			
				|  |  | +		reject: [:each | (each category match: '^\*')]
 | 
	
		
			
				|  |  |  !
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -exportPackage: packageName
 | 
	
		
			
				|  |  | -	"Export a given package by name."
 | 
	
		
			
				|  |  | +ownMethodsOfMetaClass: aClass
 | 
	
		
			
				|  |  | +	"Issue #143: sort methods alphabetically"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	| package |
 | 
	
		
			
				|  |  | -	^String streamContents: [:stream |
 | 
	
		
			
				|  |  | -		package := Smalltalk current packageAt: packageName.
 | 
	
		
			
				|  |  | -		self exportPackagePrologueOf: package on: stream.
 | 
	
		
			
				|  |  | -		[
 | 
	
		
			
				|  |  | -			self exportPackageDefinitionOf: package on: stream.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -			"Export classes in dependency order.
 | 
	
		
			
				|  |  | -			Update (issue #171): Remove duplicates for export"
 | 
	
		
			
				|  |  | -			package sortedClasses asSet do: [:each |
 | 
	
		
			
				|  |  | -						stream nextPutAll: (self exportClass: each)].
 | 
	
		
			
				|  |  | -			self exportPackageExtensionsOf: package on: stream
 | 
	
		
			
				|  |  | -		] ensure: [
 | 
	
		
			
				|  |  | -			self exportPackageEpilogueOn: stream
 | 
	
		
			
				|  |  | -		]]
 | 
	
		
			
				|  |  | +	^self ownMethodsOfClass: aClass class
 | 
	
		
			
				|  |  |  ! !
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -!Exporter methodsFor: 'private'!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -classNameFor: aClass
 | 
	
		
			
				|  |  | -	^aClass isMetaclass
 | 
	
		
			
				|  |  | -		ifTrue: [aClass instanceClass name, '.klass']
 | 
	
		
			
				|  |  | -		ifFalse: [
 | 
	
		
			
				|  |  | -		aClass isNil
 | 
	
		
			
				|  |  | -			ifTrue: ['nil']
 | 
	
		
			
				|  |  | -			ifFalse: [aClass name]]
 | 
	
		
			
				|  |  | -!
 | 
	
		
			
				|  |  | +!Exporter class methodsFor: 'exporting-output'!
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  exportDefinitionOf: aClass on: aStream
 | 
	
		
			
				|  |  |  	aStream
 | 
	
		
			
				|  |  | +		lf;
 | 
	
		
			
				|  |  |  		nextPutAll: 'smalltalk.addClass(';
 | 
	
		
			
				|  |  |  		nextPutAll: '''', (self classNameFor: aClass), ''', ';
 | 
	
		
			
				|  |  |  		nextPutAll: 'smalltalk.', (self classNameFor: aClass superclass);
 | 
	
	
		
			
				|  | @@ -140,6 +294,7 @@ exportDefinitionOf: aClass on: aStream
 | 
	
		
			
				|  |  |  !
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  exportMetaDefinitionOf: aClass on: aStream
 | 
	
		
			
				|  |  | +	aStream lf.
 | 
	
		
			
				|  |  |  	aClass class instanceVariableNames isEmpty ifFalse: [
 | 
	
		
			
				|  |  |  		aStream
 | 
	
		
			
				|  |  |  		nextPutAll: 'smalltalk.', (self classNameFor: aClass class);
 | 
	
	
		
			
				|  | @@ -150,7 +305,7 @@ exportMetaDefinitionOf: aClass on: aStream
 | 
	
		
			
				|  |  |  		aStream nextPutAll: '];', String lf]
 | 
	
		
			
				|  |  |  !
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -exportMethod: aMethod of: aClass on: aStream
 | 
	
		
			
				|  |  | +exportMethod: aMethod on: aStream
 | 
	
		
			
				|  |  |  	aStream
 | 
	
		
			
				|  |  |  		nextPutAll: 'smalltalk.addMethod(';lf;
 | 
	
		
			
				|  |  |  		"nextPutAll: aMethod selector asSelector asJavascript, ',';lf;"
 | 
	
	
		
			
				|  | @@ -165,19 +320,10 @@ exportMethod: aMethod of: aClass on: aStream
 | 
	
		
			
				|  |  |  	aStream
 | 
	
		
			
				|  |  |  		lf;
 | 
	
		
			
				|  |  |  		nextPutAll: '}),';lf;
 | 
	
		
			
				|  |  | -		nextPutAll: 'smalltalk.', (self classNameFor: aClass);
 | 
	
		
			
				|  |  | +		nextPutAll: 'smalltalk.', (self classNameFor: aMethod methodClass);
 | 
	
		
			
				|  |  |  		nextPutAll: ');';lf;lf
 | 
	
		
			
				|  |  |  !
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -exportMethodsOf: aClass on: aStream
 | 
	
		
			
				|  |  | -	"Issue #143: sort methods alphabetically"
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	((aClass methodDictionary values) sorted: [:a :b | a selector <= b selector]) do: [:each |
 | 
	
		
			
				|  |  | -		(each category match: '^\*') ifFalse: [
 | 
	
		
			
				|  |  | -			self exportMethod: each of: aClass on: aStream]].
 | 
	
		
			
				|  |  | -	aStream lf
 | 
	
		
			
				|  |  | -!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  exportPackageDefinitionOf: package on: aStream
 | 
	
		
			
				|  |  |  	aStream
 | 
	
		
			
				|  |  |  		nextPutAll: 'smalltalk.addPackage(';
 | 
	
	
		
			
				|  | @@ -185,154 +331,52 @@ exportPackageDefinitionOf: package on: aStream
 | 
	
		
			
				|  |  |  		lf
 | 
	
		
			
				|  |  |  !
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -exportPackageEpilogueOn: aStream
 | 
	
		
			
				|  |  | +exportPackageEpilogueOf: aPackage on: aStream
 | 
	
		
			
				|  |  |  	aStream
 | 
	
		
			
				|  |  | -		nextPutAll: '});';
 | 
	
		
			
				|  |  | +		nextPutAll: '})(global_smalltalk,global_nil,global__st);';
 | 
	
		
			
				|  |  |  		lf
 | 
	
		
			
				|  |  |  !
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -exportPackageExtensionsOf: package on: aStream
 | 
	
		
			
				|  |  | -	"Issue #143: sort classes and methods alphabetically"
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	| name |
 | 
	
		
			
				|  |  | -	name := package name.
 | 
	
		
			
				|  |  | -	(Package sortedClasses: Smalltalk current classes) do: [:each |
 | 
	
		
			
				|  |  | -		{each. each class} do: [:aClass |
 | 
	
		
			
				|  |  | -			((aClass methodDictionary values) sorted: [:a :b | a selector <= b selector]) do: [:method |
 | 
	
		
			
				|  |  | -				(method category match: '^\*', name) ifTrue: [
 | 
	
		
			
				|  |  | -					self exportMethod: method of: aClass on: aStream ]]]]
 | 
	
		
			
				|  |  | -!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  exportPackagePrologueOf: aPackage on: aStream
 | 
	
		
			
				|  |  |  	aStream
 | 
	
		
			
				|  |  | -		nextPutAll: 'define("amber/';
 | 
	
		
			
				|  |  | -		nextPutAll: aPackage name;
 | 
	
		
			
				|  |  | -		nextPutAll: '", ["amber_vm/smalltalk","amber_vm/nil","amber_vm/_st"], function(smalltalk,nil,_st){';
 | 
	
		
			
				|  |  | +		nextPutAll: '(function(smalltalk,nil,_st){';
 | 
	
		
			
				|  |  |  		lf
 | 
	
		
			
				|  |  |  ! !
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -Exporter subclass: #ChunkExporter
 | 
	
		
			
				|  |  | -	instanceVariableNames: ''
 | 
	
		
			
				|  |  | -	package: 'Importer-Exporter'!
 | 
	
		
			
				|  |  | -!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: 'private'!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -chunkEscape: aString
 | 
	
		
			
				|  |  | -	"Replace all occurrences of !! with !!!! and trim at both ends."
 | 
	
		
			
				|  |  | +!Exporter class methodsFor: 'fileOut'!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +recipe
 | 
	
		
			
				|  |  | +	"Export a given package."
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	^{
 | 
	
		
			
				|  |  | +		AmdExporter -> #exportPackagePrologueOf:on:.
 | 
	
		
			
				|  |  | +		self -> #exportPackageDefinitionOf:on:.
 | 
	
		
			
				|  |  | +		{
 | 
	
		
			
				|  |  | +			PluggableExporter -> #ownClassesOfPackage:.
 | 
	
		
			
				|  |  | +			self -> #exportDefinitionOf:on:.
 | 
	
		
			
				|  |  | +			{
 | 
	
		
			
				|  |  | +				self -> #ownMethodsOfClass:.
 | 
	
		
			
				|  |  | +				self -> #exportMethod:on: }.
 | 
	
		
			
				|  |  | +			self -> #exportMetaDefinitionOf:on:.
 | 
	
		
			
				|  |  | +			{
 | 
	
		
			
				|  |  | +				self -> #ownMethodsOfMetaClass:.
 | 
	
		
			
				|  |  | +				self -> #exportMethod:on: } }.
 | 
	
		
			
				|  |  | +		{
 | 
	
		
			
				|  |  | +			self -> #extensionMethodsOfPackage:.
 | 
	
		
			
				|  |  | +			self -> #exportMethod:on: }.
 | 
	
		
			
				|  |  | +		AmdExporter -> #exportPackageEpilogueOf:on:
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +! !
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	^(aString replace: '!!' with: '!!!!') trimBoth
 | 
	
		
			
				|  |  | -!
 | 
	
		
			
				|  |  | +!Exporter class methodsFor: 'private'!
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  classNameFor: aClass
 | 
	
		
			
				|  |  |  	^aClass isMetaclass
 | 
	
		
			
				|  |  | -		ifTrue: [aClass instanceClass name, ' class']
 | 
	
		
			
				|  |  | +		ifTrue: [aClass instanceClass name, '.klass']
 | 
	
		
			
				|  |  |  		ifFalse: [
 | 
	
		
			
				|  |  |  		aClass isNil
 | 
	
		
			
				|  |  |  			ifTrue: ['nil']
 | 
	
		
			
				|  |  |  			ifFalse: [aClass 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 of: aClass on: aStream
 | 
	
		
			
				|  |  | -	aStream
 | 
	
		
			
				|  |  | -		lf; lf; nextPutAll: (self chunkEscape: aMethod source); lf;
 | 
	
		
			
				|  |  | -		nextPutAll: '!!'
 | 
	
		
			
				|  |  | -!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -exportMethods: methods category: category of: aClass on: aStream
 | 
	
		
			
				|  |  | -	"Issue #143: sort methods alphabetically"
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	aStream
 | 
	
		
			
				|  |  | -		nextPutAll: '!!', (self classNameFor: aClass);
 | 
	
		
			
				|  |  | -		nextPutAll: ' methodsFor: ''', category, '''!!'.
 | 
	
		
			
				|  |  | -		(methods sorted: [:a :b | a selector <= b selector]) do: [:each |
 | 
	
		
			
				|  |  | -				self exportMethod: each of: aClass on: aStream].
 | 
	
		
			
				|  |  | -	aStream nextPutAll: ' !!'; lf; lf
 | 
	
		
			
				|  |  | -!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -exportMethodsOf: aClass on: aStream
 | 
	
		
			
				|  |  | -	"Issue #143: sort protocol alphabetically"
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	| map |
 | 
	
		
			
				|  |  | -	map := Dictionary new.
 | 
	
		
			
				|  |  | -	aClass protocolsDo: [:category :methods |
 | 
	
		
			
				|  |  | -		(category match: '^\*') ifFalse: [ map at: category put: methods ]].
 | 
	
		
			
				|  |  | -	(map keys sorted: [:a :b | a <= b ]) do: [:category | | methods |
 | 
	
		
			
				|  |  | -		methods := map at: category.
 | 
	
		
			
				|  |  | -		self
 | 
	
		
			
				|  |  | -			exportMethods: methods
 | 
	
		
			
				|  |  | -			category: category
 | 
	
		
			
				|  |  | -			of: aClass
 | 
	
		
			
				|  |  | -			on: aStream ]
 | 
	
		
			
				|  |  | -!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -exportPackageDefinitionOf: package on: aStream
 | 
	
		
			
				|  |  | -	"Chunk format."
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	aStream
 | 
	
		
			
				|  |  | -		nextPutAll: 'Smalltalk current createPackage: ''', package name, '''!!';
 | 
	
		
			
				|  |  | -		lf
 | 
	
		
			
				|  |  | -!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -exportPackageEpilogueOn: aStream
 | 
	
		
			
				|  |  | -!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -exportPackageExtensionsOf: package on: aStream
 | 
	
		
			
				|  |  | -	"We need to override this one too since we need to group
 | 
	
		
			
				|  |  | -	all methods in a given protocol under a leading methodsFor: chunk
 | 
	
		
			
				|  |  | -	for that class."
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	"Issue #143: sort protocol alphabetically"
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	| name map |
 | 
	
		
			
				|  |  | -	name := package name.
 | 
	
		
			
				|  |  | -	(Package sortedClasses: Smalltalk current classes) do: [:each |
 | 
	
		
			
				|  |  | -		{each. each class} do: [:aClass |
 | 
	
		
			
				|  |  | -			map := Dictionary new.
 | 
	
		
			
				|  |  | -			aClass protocolsDo: [:category :methods |
 | 
	
		
			
				|  |  | -				(category match: '^\*', name) ifTrue: [ map at: category put: methods ]].
 | 
	
		
			
				|  |  | -			(map keys sorted: [:a :b | a <= b ]) do: [:category | | methods |
 | 
	
		
			
				|  |  | -				methods := map at: category.
 | 
	
		
			
				|  |  | -				self exportMethods: methods category: category of: aClass on: aStream ]]]
 | 
	
		
			
				|  |  | -!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -exportPackagePrologueOf: aPackage on: aStream
 | 
	
		
			
				|  |  |  ! !
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  Exporter subclass: #StrippedExporter
 | 
	
	
		
			
				|  | @@ -341,10 +385,11 @@ Exporter subclass: #StrippedExporter
 | 
	
		
			
				|  |  |  !StrippedExporter commentStamp!
 | 
	
		
			
				|  |  |  I export Amber code into a JavaScript string, but without any optional associated data like the Amber source code.!
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -!StrippedExporter methodsFor: 'private'!
 | 
	
		
			
				|  |  | +!StrippedExporter class methodsFor: 'exporting-output'!
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  exportDefinitionOf: aClass on: aStream
 | 
	
		
			
				|  |  |  	aStream
 | 
	
		
			
				|  |  | +		lf;
 | 
	
		
			
				|  |  |  		nextPutAll: 'smalltalk.addClass(';
 | 
	
		
			
				|  |  |  		nextPutAll: '''', (self classNameFor: aClass), ''', ';
 | 
	
		
			
				|  |  |  		nextPutAll: 'smalltalk.', (self classNameFor: aClass superclass);
 | 
	
	
		
			
				|  | @@ -359,7 +404,7 @@ exportDefinitionOf: aClass on: aStream
 | 
	
		
			
				|  |  |  	aStream lf
 | 
	
		
			
				|  |  |  !
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -exportMethod: aMethod of: aClass on: aStream
 | 
	
		
			
				|  |  | +exportMethod: aMethod on: aStream
 | 
	
		
			
				|  |  |  	aStream
 | 
	
		
			
				|  |  |  		nextPutAll: 'smalltalk.addMethod(';lf;
 | 
	
		
			
				|  |  |  		"nextPutAll: aMethod selector asSelector asJavascript, ',';lf;"
 | 
	
	
		
			
				|  | @@ -368,7 +413,7 @@ exportMethod: aMethod of: aClass on: aStream
 | 
	
		
			
				|  |  |  		nextPutAll: 'fn: ', aMethod fn compiledSource, ',';lf;
 | 
	
		
			
				|  |  |  		nextPutAll: 'messageSends: ', aMethod messageSends asJavascript;
 | 
	
		
			
				|  |  |  		nextPutAll: '}),';lf;
 | 
	
		
			
				|  |  | -		nextPutAll: 'smalltalk.', (self classNameFor: aClass);
 | 
	
		
			
				|  |  | +		nextPutAll: 'smalltalk.', (self classNameFor: aMethod methodClass);
 | 
	
		
			
				|  |  |  		nextPutAll: ');';lf;lf
 | 
	
		
			
				|  |  |  ! !
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -414,7 +459,8 @@ commit: aPackage
 | 
	
		
			
				|  |  |  	self commitChannels
 | 
	
		
			
				|  |  |  		do: [ :commitStrategyFactory || fileContents commitStrategy |
 | 
	
		
			
				|  |  |  			commitStrategy := commitStrategyFactory value: aPackage.
 | 
	
		
			
				|  |  | -			fileContents := (commitStrategy key exportPackage: aPackage name).
 | 
	
		
			
				|  |  | +			fileContents := String streamContents: [ :stream |
 | 
	
		
			
				|  |  | +				(PluggableExporter newUsing: commitStrategy key) exportPackage: aPackage on: stream ].
 | 
	
		
			
				|  |  |  			self ajaxPutAt: commitStrategy value data: fileContents ]
 | 
	
		
			
				|  |  |  		displayingProgress: 'Committing package ', aPackage name
 | 
	
		
			
				|  |  |  ! !
 | 
	
	
		
			
				|  | @@ -472,9 +518,9 @@ I should not be used directly. Instead, use the corresponding `Package` methods.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  commitChannels
 | 
	
		
			
				|  |  |  	^{ 
 | 
	
		
			
				|  |  | -		[ :pkg | Exporter new -> (pkg commitPathJs, '/', pkg name, '.js') ].
 | 
	
		
			
				|  |  | -		[ :pkg | StrippedExporter new -> (pkg commitPathJs, '/', pkg name, '.deploy.js') ].
 | 
	
		
			
				|  |  | -		[ :pkg | ChunkExporter new -> (pkg commitPathSt, '/', pkg name, '.st') ]
 | 
	
		
			
				|  |  | +		[ :pkg | Exporter recipe -> (pkg commitPathJs, '/', pkg name, '.js') ].
 | 
	
		
			
				|  |  | +		[ :pkg | StrippedExporter recipe -> (pkg commitPathJs, '/', pkg name, '.deploy.js') ].
 | 
	
		
			
				|  |  | +		[ :pkg | ChunkExporter recipe -> (pkg commitPathSt, '/', pkg name, '.st') ]
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  !
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -566,6 +612,60 @@ loadPackages: aCollection prefix: aString
 | 
	
		
			
				|  |  |  	^ self new loadPackages: aCollection prefix: aString
 | 
	
		
			
				|  |  |  ! !
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +Object subclass: #PluggableExporter
 | 
	
		
			
				|  |  | +	instanceVariableNames: 'recipe'
 | 
	
		
			
				|  |  | +	package: 'Importer-Exporter'!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +!PluggableExporter methodsFor: 'accessing'!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +recipe
 | 
	
		
			
				|  |  | +	^recipe
 | 
	
		
			
				|  |  | +!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +recipe: anArray
 | 
	
		
			
				|  |  | +	recipe := anArray
 | 
	
		
			
				|  |  | +! !
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +!PluggableExporter methodsFor: 'fileOut'!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +export: anObject usingRecipe: anArray on: aStream
 | 
	
		
			
				|  |  | +	| args |
 | 
	
		
			
				|  |  | +	args := { anObject. aStream }.
 | 
	
		
			
				|  |  | +	anArray do: [ :each | | val |
 | 
	
		
			
				|  |  | +		val := each value.
 | 
	
		
			
				|  |  | +		val == each
 | 
	
		
			
				|  |  | +			ifFalse: [ "association"
 | 
	
		
			
				|  |  | +				each key perform: val withArguments: args ]
 | 
	
		
			
				|  |  | +			ifTrue: [ "sub-array"
 | 
	
		
			
				|  |  | +				| selection |
 | 
	
		
			
				|  |  | +				selection := each first key perform: each first value withArguments: { anObject }.
 | 
	
		
			
				|  |  | +				selection do: [ :eachPart |	self export: eachPart usingRecipe: each allButFirst on: aStream ]]]
 | 
	
		
			
				|  |  | +!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +exportAll
 | 
	
		
			
				|  |  | +	"Export all packages in the system."
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	^String streamContents: [:stream |
 | 
	
		
			
				|  |  | +		Smalltalk current packages do: [:pkg |
 | 
	
		
			
				|  |  | +		self exportPackage: pkg on: stream]]
 | 
	
		
			
				|  |  | +!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +exportPackage: aPackage on: aStream
 | 
	
		
			
				|  |  | +	self export: aPackage usingRecipe: self recipe on: aStream
 | 
	
		
			
				|  |  | +! !
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +!PluggableExporter class methodsFor: 'exporting-accessing'!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +newUsing: recipe
 | 
	
		
			
				|  |  | +	^self new recipe: recipe; yourself
 | 
	
		
			
				|  |  | +!
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +ownClassesOfPackage: package
 | 
	
		
			
				|  |  | +	"Export classes in dependency order.
 | 
	
		
			
				|  |  | +	Update (issue #171): Remove duplicates for export"
 | 
	
		
			
				|  |  | +	^package sortedClasses asSet
 | 
	
		
			
				|  |  | +! !
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  !Package methodsFor: '*Importer-Exporter'!
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  commit
 |