瀏覽代碼

Merge branch 'master' into requirejs

Conflicts:
	bin/amberc.js
	st/Importer-Exporter.st
Herbert Vojčík 10 年之前
父節點
當前提交
bf57605694
共有 4 個文件被更改,包括 1993 次插入307 次删除
  1. 2 2
      bin/amberc.js
  2. 780 95
      js/Importer-Exporter.deploy.js
  3. 736 118
      js/Importer-Exporter.js
  4. 475 92
      st/Importer-Exporter.st

+ 2 - 2
bin/amberc.js

@@ -546,10 +546,10 @@ AmberC.prototype.category_export = function() {
 		var packageObject = smalltalk.Package._named_(category);
 		packageObject._amdNamespace_(defaults.amd_namespace);
 		fs.writeFile(jsFile, smalltalk.String._streamContents_(function (stream) {
-			pluggableExporter._newUsing_(smalltalk.Exporter._amdRecipe())._exportPackage_on_(packageObject, stream); }), function(err) {
+			pluggableExporter._forRecipe_(smalltalk.Exporter._amdRecipe())._exportPackage_on_(packageObject, stream); }), function(err) {
 			if (defaults.deploy) {
 				fs.writeFile(jsFileDeploy, smalltalk.String._streamContents_(function (stream) {
-					pluggableExporter._newUsing_(smalltalk.StrippedExporter._amdRecipe())._exportPackage_on_(packageObject, stream); }), callback);
+					pluggableExporter._forRecipe_(smalltalk.StrippedExporter._amdRecipe())._exportPackage_on_(packageObject, stream); }), callback);
 			} else {
 				callback(null, null);
 			}

+ 780 - 95
js/Importer-Exporter.deploy.js

@@ -84,7 +84,325 @@ messageSends: ["nextPutAll:", "asJavascript", "name", "transportJson", "lf"]}),
 smalltalk.AmdExporter.klass);
 
 
-smalltalk.addClass('ChunkExporter', smalltalk.Object, [], 'Importer-Exporter');
+smalltalk.addClass('AbstractExporter', smalltalk.Object, [], 'Importer-Exporter');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "chunkEscape:",
+fn: function (aString){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(_st(aString)._replace_with_("!","!!"))._trimBoth();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"chunkEscape:",{aString:aString},smalltalk.AbstractExporter)})},
+messageSends: ["trimBoth", "replace:with:"]}),
+smalltalk.AbstractExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "classNameFor:",
+fn: function (aClass){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$3,$1;
+$2=_st(aClass)._isMetaclass();
+if(smalltalk.assert($2)){
+$1=_st(_st(_st(aClass)._instanceClass())._name()).__comma(" class");
+} else {
+$3=_st(aClass)._isNil();
+if(smalltalk.assert($3)){
+$1="nil";
+} else {
+$1=_st(aClass)._name();
+};
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"classNameFor:",{aClass:aClass},smalltalk.AbstractExporter)})},
+messageSends: ["ifTrue:ifFalse:", ",", "name", "instanceClass", "isNil", "isMetaclass"]}),
+smalltalk.AbstractExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "recipe",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self._subclassResponsibility();
+return self}, function($ctx1) {$ctx1.fill(self,"recipe",{},smalltalk.AbstractExporter)})},
+messageSends: ["subclassResponsibility"]}),
+smalltalk.AbstractExporter);
+
+
+smalltalk.AbstractExporter.klass.iVarNames = ['default'];
+smalltalk.addMethod(
+smalltalk.method({
+selector: "default",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@default"];
+if(($receiver = $2) == nil || $receiver == undefined){
+self["@default"]=self._new();
+$1=self["@default"];
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"default",{},smalltalk.AbstractExporter.klass)})},
+messageSends: ["ifNil:", "new"]}),
+smalltalk.AbstractExporter.klass);
+
+
+smalltalk.addClass('ChunkExporter', smalltalk.AbstractExporter, [], 'Importer-Exporter');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportCategoryEpilogueOf:on:",
+fn: function (aCategory,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=aStream;
+_st($1)._nextPutAll_(" !");
+_st($1)._lf();
+$2=_st($1)._lf();
+return self}, function($ctx1) {$ctx1.fill(self,"exportCategoryEpilogueOf:on:",{aCategory:aCategory,aStream:aStream},smalltalk.ChunkExporter)})},
+messageSends: ["nextPutAll:", "lf"]}),
+smalltalk.ChunkExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportCategoryPrologueOf:on:",
+fn: function (aCategory,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=aStream;
+_st($1)._nextPutAll_("!".__comma(self._classNameFor_(_st(aCategory)._theClass())));
+$2=_st($1)._nextPutAll_(_st(" methodsFor: '".__comma(_st(aCategory)._name())).__comma("'!"));
+return self}, function($ctx1) {$ctx1.fill(self,"exportCategoryPrologueOf:on:",{aCategory:aCategory,aStream:aStream},smalltalk.ChunkExporter)})},
+messageSends: ["nextPutAll:", ",", "classNameFor:", "theClass", "name"]}),
+smalltalk.ChunkExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportDefinitionOf:on:",
+fn: function (aClass,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$3,$4,$5,$6,$7;
+$1=aStream;
+_st($1)._nextPutAll_(self._classNameFor_(_st(aClass)._superclass()));
+_st($1)._nextPutAll_(" subclass: #".__comma(self._classNameFor_(aClass)));
+_st($1)._lf();
+_st($1)._tab();
+$2=_st($1)._nextPutAll_("instanceVariableNames: '");
+_st(_st(aClass)._instanceVariableNames())._do_separatedBy_((function(each){
+return smalltalk.withContext(function($ctx2) {
+return _st(aStream)._nextPutAll_(each);
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1)})}),(function(){
+return smalltalk.withContext(function($ctx2) {
+return _st(aStream)._nextPutAll_(" ");
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+$3=aStream;
+_st($3)._nextPutAll_("'");
+_st($3)._lf();
+_st($3)._tab();
+_st($3)._nextPutAll_(_st("package: '".__comma(_st(aClass)._category())).__comma("'!"));
+$4=_st($3)._lf();
+$5=_st(_st(aClass)._comment())._notEmpty();
+if(smalltalk.assert($5)){
+$6=aStream;
+_st($6)._nextPutAll_(_st("!".__comma(self._classNameFor_(aClass))).__comma(" commentStamp!"));
+_st($6)._lf();
+_st($6)._nextPutAll_(_st(self._chunkEscape_(_st(aClass)._comment())).__comma("!"));
+$7=_st($6)._lf();
+$7;
+};
+_st(aStream)._lf();
+return self}, function($ctx1) {$ctx1.fill(self,"exportDefinitionOf:on:",{aClass:aClass,aStream:aStream},smalltalk.ChunkExporter)})},
+messageSends: ["nextPutAll:", "classNameFor:", "superclass", ",", "lf", "tab", "do:separatedBy:", "instanceVariableNames", "category", "ifTrue:", "chunkEscape:", "comment", "notEmpty"]}),
+smalltalk.ChunkExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportMetaDefinitionOf:on:",
+fn: function (aClass,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$3,$4,$5;
+$1=_st(_st(_st(aClass)._class())._instanceVariableNames())._isEmpty();
+if(! smalltalk.assert($1)){
+$2=aStream;
+_st($2)._nextPutAll_(self._classNameFor_(_st(aClass)._class()));
+$3=_st($2)._nextPutAll_(" instanceVariableNames: '");
+$3;
+_st(_st(_st(aClass)._class())._instanceVariableNames())._do_separatedBy_((function(each){
+return smalltalk.withContext(function($ctx2) {
+return _st(aStream)._nextPutAll_(each);
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1)})}),(function(){
+return smalltalk.withContext(function($ctx2) {
+return _st(aStream)._nextPutAll_(" ");
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+$4=aStream;
+_st($4)._nextPutAll_("'!");
+_st($4)._lf();
+$5=_st($4)._lf();
+$5;
+};
+return self}, function($ctx1) {$ctx1.fill(self,"exportMetaDefinitionOf:on:",{aClass:aClass,aStream:aStream},smalltalk.ChunkExporter)})},
+messageSends: ["ifFalse:", "nextPutAll:", "classNameFor:", "class", "do:separatedBy:", "instanceVariableNames", "lf", "isEmpty"]}),
+smalltalk.ChunkExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportMethod:on:",
+fn: function (aMethod,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=aStream;
+_st($1)._lf();
+_st($1)._lf();
+_st($1)._nextPutAll_(self._chunkEscape_(_st(aMethod)._source()));
+_st($1)._lf();
+$2=_st($1)._nextPutAll_("!");
+return self}, function($ctx1) {$ctx1.fill(self,"exportMethod:on:",{aMethod:aMethod,aStream:aStream},smalltalk.ChunkExporter)})},
+messageSends: ["lf", "nextPutAll:", "chunkEscape:", "source"]}),
+smalltalk.ChunkExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportPackageDefinitionOf:on:",
+fn: function (aPackage,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=aStream;
+_st($1)._nextPutAll_(_st("Smalltalk current createPackage: '".__comma(_st(aPackage)._name())).__comma("'!"));
+$2=_st($1)._lf();
+return self}, function($ctx1) {$ctx1.fill(self,"exportPackageDefinitionOf:on:",{aPackage:aPackage,aStream:aStream},smalltalk.ChunkExporter)})},
+messageSends: ["nextPutAll:", ",", "name", "lf"]}),
+smalltalk.ChunkExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "extensionCategoriesOfPackage:",
+fn: function (aPackage){
+var self=this;
+var name,map,result;
+function $OrderedCollection(){return smalltalk.OrderedCollection||(typeof OrderedCollection=="undefined"?nil:OrderedCollection)}
+function $Dictionary(){return smalltalk.Dictionary||(typeof Dictionary=="undefined"?nil:Dictionary)}
+function $MethodCategory(){return smalltalk.MethodCategory||(typeof MethodCategory=="undefined"?nil:MethodCategory)}
+function $Smalltalk(){return smalltalk.Smalltalk||(typeof Smalltalk=="undefined"?nil:Smalltalk)}
+function $Package(){return smalltalk.Package||(typeof Package=="undefined"?nil:Package)}
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+name=_st(aPackage)._name();
+result=_st($OrderedCollection())._new();
+_st(_st($Package())._sortedClasses_(_st(_st($Smalltalk())._current())._classes()))._do_((function(each){
+return smalltalk.withContext(function($ctx2) {
+return _st([each,_st(each)._class()])._do_((function(aClass){
+return smalltalk.withContext(function($ctx3) {
+map=_st($Dictionary())._new();
+map;
+_st(aClass)._protocolsDo_((function(category,methods){
+return smalltalk.withContext(function($ctx4) {
+$1=_st(category)._match_("^\x5c*".__comma(name));
+if(smalltalk.assert($1)){
+return _st(map)._at_put_(category,methods);
+};
+}, function($ctx4) {$ctx4.fillBlock({category:category,methods:methods},$ctx3)})}));
+return _st(result)._addAll_(_st(_st(_st(map)._keys())._sorted_((function(a,b){
+return smalltalk.withContext(function($ctx4) {
+return _st(a).__lt_eq(b);
+}, function($ctx4) {$ctx4.fillBlock({a:a,b:b},$ctx3)})})))._collect_((function(category){
+return smalltalk.withContext(function($ctx4) {
+return _st($MethodCategory())._name_theClass_methods_(category,aClass,_st(map)._at_(category));
+}, function($ctx4) {$ctx4.fillBlock({category:category},$ctx3)})})));
+}, function($ctx3) {$ctx3.fillBlock({aClass:aClass},$ctx2)})}));
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1)})}));
+$2=result;
+return $2;
+}, function($ctx1) {$ctx1.fill(self,"extensionCategoriesOfPackage:",{aPackage:aPackage,name:name,map:map,result:result},smalltalk.ChunkExporter)})},
+messageSends: ["name", "new", "do:", "protocolsDo:", "ifTrue:", "at:put:", "match:", ",", "addAll:", "collect:", "name:theClass:methods:", "at:", "sorted:", "<=", "keys", "class", "sortedClasses:", "classes", "current"]}),
+smalltalk.ChunkExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "methodsOfCategory:",
+fn: function (aCategory){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(_st(aCategory)._methods())._sorted_((function(a,b){
+return smalltalk.withContext(function($ctx2) {
+return _st(_st(a)._selector()).__lt_eq(_st(b)._selector());
+}, function($ctx2) {$ctx2.fillBlock({a:a,b:b},$ctx1)})}));
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"methodsOfCategory:",{aCategory:aCategory},smalltalk.ChunkExporter)})},
+messageSends: ["sorted:", "<=", "selector", "methods"]}),
+smalltalk.ChunkExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "ownCategoriesOfClass:",
+fn: function (aClass){
+var self=this;
+var map;
+function $Dictionary(){return smalltalk.Dictionary||(typeof Dictionary=="undefined"?nil:Dictionary)}
+function $MethodCategory(){return smalltalk.MethodCategory||(typeof MethodCategory=="undefined"?nil:MethodCategory)}
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+map=_st($Dictionary())._new();
+_st(aClass)._protocolsDo_((function(category,methods){
+return smalltalk.withContext(function($ctx2) {
+$1=_st(category)._match_("^\x5c*");
+if(! smalltalk.assert($1)){
+return _st(map)._at_put_(category,methods);
+};
+}, function($ctx2) {$ctx2.fillBlock({category:category,methods:methods},$ctx1)})}));
+$2=_st(_st(_st(map)._keys())._sorted_((function(a,b){
+return smalltalk.withContext(function($ctx2) {
+return _st(a).__lt_eq(b);
+}, function($ctx2) {$ctx2.fillBlock({a:a,b:b},$ctx1)})})))._collect_((function(category){
+return smalltalk.withContext(function($ctx2) {
+return _st($MethodCategory())._name_theClass_methods_(category,aClass,_st(map)._at_(category));
+}, function($ctx2) {$ctx2.fillBlock({category:category},$ctx1)})}));
+return $2;
+}, function($ctx1) {$ctx1.fill(self,"ownCategoriesOfClass:",{aClass:aClass,map:map},smalltalk.ChunkExporter)})},
+messageSends: ["new", "protocolsDo:", "ifFalse:", "at:put:", "match:", "collect:", "name:theClass:methods:", "at:", "sorted:", "<=", "keys"]}),
+smalltalk.ChunkExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "ownCategoriesOfMetaClass:",
+fn: function (aClass){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self._ownCategoriesOfClass_(_st(aClass)._class());
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"ownCategoriesOfMetaClass:",{aClass:aClass},smalltalk.ChunkExporter)})},
+messageSends: ["ownCategoriesOfClass:", "class"]}),
+smalltalk.ChunkExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "recipe",
+fn: function (){
+var self=this;
+var exportCategoryRecipe;
+function $PluggableExporter(){return smalltalk.PluggableExporter||(typeof PluggableExporter=="undefined"?nil:PluggableExporter)}
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+exportCategoryRecipe=[self.__minus_gt("exportCategoryPrologueOf:on:"),[self.__minus_gt("methodsOfCategory:"),self.__minus_gt("exportMethod:on:")],self.__minus_gt("exportCategoryEpilogueOf:on:")];
+$1=[self.__minus_gt("exportPackageDefinitionOf:on:"),[_st($PluggableExporter()).__minus_gt("ownClassesOfPackage:"),self.__minus_gt("exportDefinitionOf:on:"),_st([self.__minus_gt("ownCategoriesOfClass:")]).__comma(exportCategoryRecipe),self.__minus_gt("exportMetaDefinitionOf:on:"),_st([self.__minus_gt("ownCategoriesOfMetaClass:")]).__comma(exportCategoryRecipe)],_st([self.__minus_gt("extensionCategoriesOfPackage:")]).__comma(exportCategoryRecipe)];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"recipe",{exportCategoryRecipe:exportCategoryRecipe},smalltalk.ChunkExporter)})},
+messageSends: ["->", ","]}),
+smalltalk.ChunkExporter);
+
 
 smalltalk.addMethod(
 smalltalk.method({
@@ -371,71 +689,256 @@ messageSends: ["->", ","]}),
 smalltalk.ChunkExporter.klass);
 
 
-smalltalk.addClass('ChunkParser', smalltalk.Object, ['stream'], 'Importer-Exporter');
+smalltalk.addClass('Exporter', smalltalk.AbstractExporter, [], 'Importer-Exporter');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "classNameFor:",
+fn: function (aClass){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$3,$1;
+$2=_st(aClass)._isMetaclass();
+if(smalltalk.assert($2)){
+$1=_st(_st(_st(aClass)._instanceClass())._name()).__comma(".klass");
+} else {
+$3=_st(aClass)._isNil();
+if(smalltalk.assert($3)){
+$1="nil";
+} else {
+$1=_st(aClass)._name();
+};
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"classNameFor:",{aClass:aClass},smalltalk.Exporter)})},
+messageSends: ["ifTrue:ifFalse:", ",", "name", "instanceClass", "isNil", "isMetaclass"]}),
+smalltalk.Exporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportDefinitionOf:on:",
+fn: function (aClass,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$3,$4,$5,$6,$7;
+$1=aStream;
+_st($1)._lf();
+_st($1)._nextPutAll_("smalltalk.addClass(");
+_st($1)._nextPutAll_(_st("'".__comma(self._classNameFor_(aClass))).__comma("', "));
+_st($1)._nextPutAll_("smalltalk.".__comma(self._classNameFor_(_st(aClass)._superclass())));
+$2=_st($1)._nextPutAll_(", [");
+_st(_st(aClass)._instanceVariableNames())._do_separatedBy_((function(each){
+return smalltalk.withContext(function($ctx2) {
+return _st(aStream)._nextPutAll_(_st("'".__comma(each)).__comma("'"));
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1)})}),(function(){
+return smalltalk.withContext(function($ctx2) {
+return _st(aStream)._nextPutAll_(", ");
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+$3=aStream;
+_st($3)._nextPutAll_("], '");
+_st($3)._nextPutAll_(_st(_st(aClass)._category()).__comma("'"));
+$4=_st($3)._nextPutAll_(");");
+$5=_st(_st(aClass)._comment())._notEmpty();
+if(smalltalk.assert($5)){
+$6=aStream;
+_st($6)._lf();
+_st($6)._nextPutAll_("smalltalk.");
+_st($6)._nextPutAll_(self._classNameFor_(aClass));
+_st($6)._nextPutAll_(".comment=");
+_st($6)._nextPutAll_(_st(_st(aClass)._comment())._asJavascript());
+$7=_st($6)._nextPutAll_(";");
+$7;
+};
+_st(aStream)._lf();
+return self}, function($ctx1) {$ctx1.fill(self,"exportDefinitionOf:on:",{aClass:aClass,aStream:aStream},smalltalk.Exporter)})},
+messageSends: ["lf", "nextPutAll:", ",", "classNameFor:", "superclass", "do:separatedBy:", "instanceVariableNames", "category", "ifTrue:", "asJavascript", "comment", "notEmpty"]}),
+smalltalk.Exporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportMetaDefinitionOf:on:",
+fn: function (aClass,aStream){
+var self=this;
+function $String(){return smalltalk.String||(typeof String=="undefined"?nil:String)}
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$3;
+_st(aStream)._lf();
+$1=_st(_st(_st(aClass)._class())._instanceVariableNames())._isEmpty();
+if(! smalltalk.assert($1)){
+$2=aStream;
+_st($2)._nextPutAll_("smalltalk.".__comma(self._classNameFor_(_st(aClass)._class())));
+$3=_st($2)._nextPutAll_(".iVarNames = [");
+$3;
+_st(_st(_st(aClass)._class())._instanceVariableNames())._do_separatedBy_((function(each){
+return smalltalk.withContext(function($ctx2) {
+return _st(aStream)._nextPutAll_(_st("'".__comma(each)).__comma("'"));
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1)})}),(function(){
+return smalltalk.withContext(function($ctx2) {
+return _st(aStream)._nextPutAll_(",");
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+_st(aStream)._nextPutAll_("];".__comma(_st($String())._lf()));
+};
+return self}, function($ctx1) {$ctx1.fill(self,"exportMetaDefinitionOf:on:",{aClass:aClass,aStream:aStream},smalltalk.Exporter)})},
+messageSends: ["lf", "ifFalse:", "nextPutAll:", ",", "classNameFor:", "class", "do:separatedBy:", "instanceVariableNames", "isEmpty"]}),
+smalltalk.Exporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportMethod:on:",
+fn: function (aMethod,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$3,$4;
+$1=aStream;
+_st($1)._nextPutAll_("smalltalk.addMethod(");
+_st($1)._lf();
+_st($1)._nextPutAll_("smalltalk.method({");
+_st($1)._lf();
+_st($1)._nextPutAll_(_st("selector: ".__comma(_st(_st(aMethod)._selector())._asJavascript())).__comma(","));
+_st($1)._lf();
+_st($1)._nextPutAll_(_st("category: '".__comma(_st(aMethod)._category())).__comma("',"));
+_st($1)._lf();
+_st($1)._nextPutAll_(_st("fn: ".__comma(_st(_st(aMethod)._fn())._compiledSource())).__comma(","));
+_st($1)._lf();
+_st($1)._nextPutAll_(_st("args: ".__comma(_st(_st(aMethod)._arguments())._asJavascript())).__comma(","));
+_st($1)._lf();
+_st($1)._nextPutAll_(_st("source: ".__comma(_st(_st(aMethod)._source())._asJavascript())).__comma(","));
+_st($1)._lf();
+_st($1)._nextPutAll_(_st("messageSends: ".__comma(_st(_st(aMethod)._messageSends())._asJavascript())).__comma(","));
+_st($1)._lf();
+$2=_st($1)._nextPutAll_("referencedClasses: ".__comma(_st(_st(aMethod)._referencedClasses())._asJavascript()));
+$3=aStream;
+_st($3)._lf();
+_st($3)._nextPutAll_("}),");
+_st($3)._lf();
+_st($3)._nextPutAll_("smalltalk.".__comma(self._classNameFor_(_st(aMethod)._methodClass())));
+_st($3)._nextPutAll_(");");
+_st($3)._lf();
+$4=_st($3)._lf();
+return self}, function($ctx1) {$ctx1.fill(self,"exportMethod:on:",{aMethod:aMethod,aStream:aStream},smalltalk.Exporter)})},
+messageSends: ["nextPutAll:", "lf", ",", "asJavascript", "selector", "category", "compiledSource", "fn", "arguments", "source", "messageSends", "referencedClasses", "classNameFor:", "methodClass"]}),
+smalltalk.Exporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportPackageDefinitionOf:on:",
+fn: function (aPackage,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=aStream;
+_st($1)._nextPutAll_("smalltalk.addPackage(");
+_st($1)._nextPutAll_(_st("'".__comma(_st(aPackage)._name())).__comma("');"));
+$2=_st($1)._lf();
+return self}, function($ctx1) {$ctx1.fill(self,"exportPackageDefinitionOf:on:",{aPackage:aPackage,aStream:aStream},smalltalk.Exporter)})},
+messageSends: ["nextPutAll:", ",", "name", "lf"]}),
+smalltalk.Exporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportPackageEpilogueOf:on:",
+fn: function (aPackage,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=aStream;
+_st($1)._nextPutAll_("})(global_smalltalk,global_nil,global__st);");
+$2=_st($1)._lf();
+return self}, function($ctx1) {$ctx1.fill(self,"exportPackageEpilogueOf:on:",{aPackage:aPackage,aStream:aStream},smalltalk.Exporter)})},
+messageSends: ["nextPutAll:", "lf"]}),
+smalltalk.Exporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportPackagePrologueOf:on:",
+fn: function (aPackage,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=aStream;
+_st($1)._nextPutAll_("(function(smalltalk,nil,_st){");
+$2=_st($1)._lf();
+return self}, function($ctx1) {$ctx1.fill(self,"exportPackagePrologueOf:on:",{aPackage:aPackage,aStream:aStream},smalltalk.Exporter)})},
+messageSends: ["nextPutAll:", "lf"]}),
+smalltalk.Exporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "extensionMethodsOfPackage:",
+fn: function (aPackage){
+var self=this;
+var name,result;
+function $OrderedCollection(){return smalltalk.OrderedCollection||(typeof OrderedCollection=="undefined"?nil:OrderedCollection)}
+function $Smalltalk(){return smalltalk.Smalltalk||(typeof Smalltalk=="undefined"?nil:Smalltalk)}
+function $Package(){return smalltalk.Package||(typeof Package=="undefined"?nil:Package)}
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+name=_st(aPackage)._name();
+result=_st($OrderedCollection())._new();
+_st(_st($Package())._sortedClasses_(_st(_st($Smalltalk())._current())._classes()))._do_((function(each){
+return smalltalk.withContext(function($ctx2) {
+return _st([each,_st(each)._class()])._do_((function(aClass){
+return smalltalk.withContext(function($ctx3) {
+return _st(result)._addAll_(_st(_st(_st(_st(aClass)._methodDictionary())._values())._sorted_((function(a,b){
+return smalltalk.withContext(function($ctx4) {
+return _st(_st(a)._selector()).__lt_eq(_st(b)._selector());
+}, function($ctx4) {$ctx4.fillBlock({a:a,b:b},$ctx3)})})))._select_((function(method){
+return smalltalk.withContext(function($ctx4) {
+return _st(_st(method)._category())._match_("^\x5c*".__comma(name));
+}, function($ctx4) {$ctx4.fillBlock({method:method},$ctx3)})})));
+}, function($ctx3) {$ctx3.fillBlock({aClass:aClass},$ctx2)})}));
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1)})}));
+$1=result;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"extensionMethodsOfPackage:",{aPackage:aPackage,name:name,result:result},smalltalk.Exporter)})},
+messageSends: ["name", "new", "do:", "addAll:", "select:", "match:", ",", "category", "sorted:", "<=", "selector", "values", "methodDictionary", "class", "sortedClasses:", "classes", "current"]}),
+smalltalk.Exporter);
+
 smalltalk.addMethod(
 smalltalk.method({
-selector: "nextChunk",
-fn: function (){
+selector: "ownMethodsOfClass:",
+fn: function (aClass){
 var self=this;
-var char,result,chunk;
 return smalltalk.withContext(function($ctx1) { 
-var $1,$2,$3;
-var $early={};
-try {
-result=""._writeStream();
-_st((function(){
+var $1;
+$1=_st(_st(_st(_st(aClass)._methodDictionary())._values())._sorted_((function(a,b){
 return smalltalk.withContext(function($ctx2) {
-char=_st(self["@stream"])._next();
-char;
-return _st(char)._notNil();
-}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}))._whileTrue_((function(){
+return _st(_st(a)._selector()).__lt_eq(_st(b)._selector());
+}, function($ctx2) {$ctx2.fillBlock({a:a,b:b},$ctx1)})})))._reject_((function(each){
 return smalltalk.withContext(function($ctx2) {
-$1=_st(char).__eq("!");
-if(smalltalk.assert($1)){
-$2=_st(_st(self["@stream"])._peek()).__eq("!");
-if(smalltalk.assert($2)){
-_st(self["@stream"])._next();
-} else {
-$3=_st(_st(result)._contents())._trimBoth();
-throw $early=[$3];
-};
-};
-return _st(result)._nextPut_(char);
-}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
-return nil;
-}
-catch(e) {if(e===$early)return e[0]; throw e}
-}, function($ctx1) {$ctx1.fill(self,"nextChunk",{char:char,result:result,chunk:chunk},smalltalk.ChunkParser)})},
-messageSends: ["writeStream", "whileTrue:", "ifTrue:", "ifTrue:ifFalse:", "next", "trimBoth", "contents", "=", "peek", "nextPut:", "notNil"]}),
-smalltalk.ChunkParser);
+return _st(_st(each)._category())._match_("^\x5c*");
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1)})}));
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"ownMethodsOfClass:",{aClass:aClass},smalltalk.Exporter)})},
+messageSends: ["reject:", "match:", "category", "sorted:", "<=", "selector", "values", "methodDictionary"]}),
+smalltalk.Exporter);
 
 smalltalk.addMethod(
 smalltalk.method({
-selector: "stream:",
-fn: function (aStream){
+selector: "ownMethodsOfMetaClass:",
+fn: function (aClass){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-self["@stream"]=aStream;
-return self}, function($ctx1) {$ctx1.fill(self,"stream:",{aStream:aStream},smalltalk.ChunkParser)})},
-messageSends: []}),
-smalltalk.ChunkParser);
-
+var $1;
+$1=self._ownMethodsOfClass_(_st(aClass)._class());
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"ownMethodsOfMetaClass:",{aClass:aClass},smalltalk.Exporter)})},
+messageSends: ["ownMethodsOfClass:", "class"]}),
+smalltalk.Exporter);
 
 smalltalk.addMethod(
 smalltalk.method({
-selector: "on:",
-fn: function (aStream){
+selector: "recipe",
+fn: function (){
 var self=this;
+function $PluggableExporter(){return smalltalk.PluggableExporter||(typeof PluggableExporter=="undefined"?nil:PluggableExporter)}
 return smalltalk.withContext(function($ctx1) { 
 var $1;
-$1=_st(self._new())._stream_(aStream);
+$1=[self.__minus_gt("exportPackagePrologueOf:on:"),self.__minus_gt("exportPackageDefinitionOf:on:"),[_st($PluggableExporter()).__minus_gt("ownClassesOfPackage:"),self.__minus_gt("exportDefinitionOf:on:"),[self.__minus_gt("ownMethodsOfClass:"),self.__minus_gt("exportMethod:on:")],self.__minus_gt("exportMetaDefinitionOf:on:"),[self.__minus_gt("ownMethodsOfMetaClass:"),self.__minus_gt("exportMethod:on:")]],[self.__minus_gt("extensionMethodsOfPackage:"),self.__minus_gt("exportMethod:on:")],self.__minus_gt("exportPackageEpilogueOf:on:")];
 return $1;
-}, function($ctx1) {$ctx1.fill(self,"on:",{aStream:aStream},smalltalk.ChunkParser.klass)})},
-messageSends: ["stream:", "new"]}),
-smalltalk.ChunkParser.klass);
-
+}, function($ctx1) {$ctx1.fill(self,"recipe",{},smalltalk.Exporter)})},
+messageSends: ["->"]}),
+smalltalk.Exporter);
 
-smalltalk.addClass('Exporter', smalltalk.Object, [], 'Importer-Exporter');
 
 smalltalk.addMethod(
 smalltalk.method({
@@ -705,6 +1208,62 @@ smalltalk.Exporter.klass);
 
 
 smalltalk.addClass('StrippedExporter', smalltalk.Exporter, [], 'Importer-Exporter');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportDefinitionOf:on:",
+fn: function (aClass,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$3,$4;
+$1=aStream;
+_st($1)._lf();
+_st($1)._nextPutAll_("smalltalk.addClass(");
+_st($1)._nextPutAll_(_st("'".__comma(self._classNameFor_(aClass))).__comma("', "));
+_st($1)._nextPutAll_("smalltalk.".__comma(self._classNameFor_(_st(aClass)._superclass())));
+$2=_st($1)._nextPutAll_(", [");
+_st(_st(aClass)._instanceVariableNames())._do_separatedBy_((function(each){
+return smalltalk.withContext(function($ctx2) {
+return _st(aStream)._nextPutAll_(_st("'".__comma(each)).__comma("'"));
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1)})}),(function(){
+return smalltalk.withContext(function($ctx2) {
+return _st(aStream)._nextPutAll_(", ");
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+$3=aStream;
+_st($3)._nextPutAll_("], '");
+_st($3)._nextPutAll_(_st(_st(aClass)._category()).__comma("'"));
+$4=_st($3)._nextPutAll_(");");
+_st(aStream)._lf();
+return self}, function($ctx1) {$ctx1.fill(self,"exportDefinitionOf:on:",{aClass:aClass,aStream:aStream},smalltalk.StrippedExporter)})},
+messageSends: ["lf", "nextPutAll:", ",", "classNameFor:", "superclass", "do:separatedBy:", "instanceVariableNames", "category"]}),
+smalltalk.StrippedExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "exportMethod:on:",
+fn: function (aMethod,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=aStream;
+_st($1)._nextPutAll_("smalltalk.addMethod(");
+_st($1)._lf();
+_st($1)._nextPutAll_("smalltalk.method({");
+_st($1)._lf();
+_st($1)._nextPutAll_(_st("selector: ".__comma(_st(_st(aMethod)._selector())._asJavascript())).__comma(","));
+_st($1)._lf();
+_st($1)._nextPutAll_(_st("fn: ".__comma(_st(_st(aMethod)._fn())._compiledSource())).__comma(","));
+_st($1)._lf();
+_st($1)._nextPutAll_("messageSends: ".__comma(_st(_st(aMethod)._messageSends())._asJavascript()));
+_st($1)._nextPutAll_("}),");
+_st($1)._lf();
+_st($1)._nextPutAll_("smalltalk.".__comma(self._classNameFor_(_st(aMethod)._methodClass())));
+_st($1)._nextPutAll_(");");
+_st($1)._lf();
+$2=_st($1)._lf();
+return self}, function($ctx1) {$ctx1.fill(self,"exportMethod:on:",{aMethod:aMethod,aStream:aStream},smalltalk.StrippedExporter)})},
+messageSends: ["nextPutAll:", "lf", ",", "asJavascript", "selector", "compiledSource", "fn", "messageSends", "classNameFor:", "methodClass"]}),
+smalltalk.StrippedExporter);
+
 
 smalltalk.addMethod(
 smalltalk.method({
@@ -763,6 +1322,70 @@ messageSends: ["nextPutAll:", "lf", ",", "asJavascript", "selector", "compiledSo
 smalltalk.StrippedExporter.klass);
 
 
+smalltalk.addClass('ChunkParser', smalltalk.Object, ['stream'], 'Importer-Exporter');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "nextChunk",
+fn: function (){
+var self=this;
+var char,result,chunk;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$3;
+var $early={};
+try {
+result=""._writeStream();
+_st((function(){
+return smalltalk.withContext(function($ctx2) {
+char=_st(self["@stream"])._next();
+char;
+return _st(char)._notNil();
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}))._whileTrue_((function(){
+return smalltalk.withContext(function($ctx2) {
+$1=_st(char).__eq("!");
+if(smalltalk.assert($1)){
+$2=_st(_st(self["@stream"])._peek()).__eq("!");
+if(smalltalk.assert($2)){
+_st(self["@stream"])._next();
+} else {
+$3=_st(_st(result)._contents())._trimBoth();
+throw $early=[$3];
+};
+};
+return _st(result)._nextPut_(char);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+return nil;
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+}, function($ctx1) {$ctx1.fill(self,"nextChunk",{char:char,result:result,chunk:chunk},smalltalk.ChunkParser)})},
+messageSends: ["writeStream", "whileTrue:", "ifTrue:", "ifTrue:ifFalse:", "next", "trimBoth", "contents", "=", "peek", "nextPut:", "notNil"]}),
+smalltalk.ChunkParser);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "stream:",
+fn: function (aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@stream"]=aStream;
+return self}, function($ctx1) {$ctx1.fill(self,"stream:",{aStream:aStream},smalltalk.ChunkParser)})},
+messageSends: []}),
+smalltalk.ChunkParser);
+
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "on:",
+fn: function (aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(self._new())._stream_(aStream);
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"on:",{aStream:aStream},smalltalk.ChunkParser.klass)})},
+messageSends: ["stream:", "new"]}),
+smalltalk.ChunkParser.klass);
+
+
 smalltalk.addClass('Importer', smalltalk.Object, [], 'Importer-Exporter');
 smalltalk.addMethod(
 smalltalk.method({
@@ -821,11 +1444,11 @@ smalltalk.MethodCategory);
 smalltalk.addMethod(
 smalltalk.method({
 selector: "methods:",
-fn: function (anArray){
+fn: function (aCollection){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-self["@methods"]=anArray;
-return self}, function($ctx1) {$ctx1.fill(self,"methods:",{anArray:anArray},smalltalk.MethodCategory)})},
+self["@methods"]=aCollection;
+return self}, function($ctx1) {$ctx1.fill(self,"methods:",{aCollection:aCollection},smalltalk.MethodCategory)})},
 messageSends: []}),
 smalltalk.MethodCategory);
 
@@ -927,13 +1550,13 @@ commitStrategy=_st(commitStrategyFactory)._value_(aPackage);
 commitStrategy;
 fileContents=_st($String())._streamContents_((function(stream){
 return smalltalk.withContext(function($ctx3) {
-return _st(_st($PluggableExporter())._newUsing_(_st(commitStrategy)._key()))._exportPackage_on_(aPackage,stream);
+return _st(_st($PluggableExporter())._forRecipe_(_st(commitStrategy)._key()))._exportPackage_on_(aPackage,stream);
 }, function($ctx3) {$ctx3.fillBlock({stream:stream},$ctx2)})}));
 fileContents;
 return self._ajaxPutAt_data_(_st(commitStrategy)._value(),fileContents);
 }, function($ctx2) {$ctx2.fillBlock({commitStrategyFactory:commitStrategyFactory,fileContents:fileContents,commitStrategy:commitStrategy},$ctx1)})}),"Committing package ".__comma(_st(aPackage)._name()));
 return self}, function($ctx1) {$ctx1.fill(self,"commit:",{aPackage:aPackage},smalltalk.PackageHandler)})},
-messageSends: ["do:displayingProgress:", "value:", "streamContents:", "exportPackage:on:", "newUsing:", "key", "ajaxPutAt:data:", "value", ",", "name", "commitChannels"]}),
+messageSends: ["do:displayingProgress:", "value:", "streamContents:", "exportPackage:on:", "forRecipe:", "key", "ajaxPutAt:data:", "value", ",", "name", "commitChannels"]}),
 smalltalk.PackageHandler);
 
 smalltalk.addMethod(
@@ -1184,17 +1807,17 @@ return smalltalk.withContext(function($ctx1) {
 var $1;
 $1=[(function(pkg){
 return smalltalk.withContext(function($ctx2) {
-return _st(_st($Exporter())._recipe()).__minus_gt(_st(_st(_st(_st(pkg)._commitPathJs()).__comma("/")).__comma(_st(pkg)._name())).__comma(".js"));
+return _st(_st(_st($Exporter())._default())._recipe()).__minus_gt(_st(_st(_st(_st(pkg)._commitPathJs()).__comma("/")).__comma(_st(pkg)._name())).__comma(".js"));
 }, function($ctx2) {$ctx2.fillBlock({pkg:pkg},$ctx1)})}),(function(pkg){
 return smalltalk.withContext(function($ctx2) {
-return _st(_st($StrippedExporter())._recipe()).__minus_gt(_st(_st(_st(_st(pkg)._commitPathJs()).__comma("/")).__comma(_st(pkg)._name())).__comma(".deploy.js"));
+return _st(_st(_st($StrippedExporter())._default())._recipe()).__minus_gt(_st(_st(_st(_st(pkg)._commitPathJs()).__comma("/")).__comma(_st(pkg)._name())).__comma(".deploy.js"));
 }, function($ctx2) {$ctx2.fillBlock({pkg:pkg},$ctx1)})}),(function(pkg){
 return smalltalk.withContext(function($ctx2) {
-return _st(_st($ChunkExporter())._recipe()).__minus_gt(_st(_st(_st(_st(pkg)._commitPathSt()).__comma("/")).__comma(_st(pkg)._name())).__comma(".st"));
+return _st(_st(_st($ChunkExporter())._default())._recipe()).__minus_gt(_st(_st(_st(_st(pkg)._commitPathSt()).__comma("/")).__comma(_st(pkg)._name())).__comma(".st"));
 }, function($ctx2) {$ctx2.fillBlock({pkg:pkg},$ctx1)})})];
 return $1;
 }, function($ctx1) {$ctx1.fill(self,"commitChannels",{},smalltalk.LegacyPackageHandler)})},
-messageSends: ["->", ",", "name", "commitPathJs", "recipe", "commitPathSt"]}),
+messageSends: ["->", ",", "name", "commitPathJs", "recipe", "default", "commitPathSt"]}),
 smalltalk.LegacyPackageHandler);
 
 smalltalk.addMethod(
@@ -1396,38 +2019,7 @@ smalltalk.LegacyPackageHandler.klass);
 smalltalk.addClass('PluggableExporter', smalltalk.Object, ['recipe'], 'Importer-Exporter');
 smalltalk.addMethod(
 smalltalk.method({
-selector: "export:usingRecipe:on:",
-fn: function (anObject,anArray,aStream){
-var self=this;
-var args;
-return smalltalk.withContext(function($ctx1) { 
-var $1;
-args=[anObject,aStream];
-_st(anArray)._do_((function(each){
-var val;
-return smalltalk.withContext(function($ctx2) {
-val=_st(each)._value();
-val;
-$1=_st(val).__eq_eq(each);
-if(smalltalk.assert($1)){
-var selection;
-selection=_st(_st(_st(each)._first())._key())._perform_withArguments_(_st(_st(each)._first())._value(),[anObject]);
-selection;
-return _st(selection)._do_((function(eachPart){
-return smalltalk.withContext(function($ctx3) {
-return self._export_usingRecipe_on_(eachPart,_st(each)._allButFirst(),aStream);
-}, function($ctx3) {$ctx3.fillBlock({eachPart:eachPart},$ctx2)})}));
-} else {
-return _st(_st(each)._key())._perform_withArguments_(val,args);
-};
-}, function($ctx2) {$ctx2.fillBlock({each:each,val:val},$ctx1)})}));
-return self}, function($ctx1) {$ctx1.fill(self,"export:usingRecipe:on:",{anObject:anObject,anArray:anArray,aStream:aStream,args:args},smalltalk.PluggableExporter)})},
-messageSends: ["do:", "value", "ifFalse:ifTrue:", "perform:withArguments:", "key", "first", "export:usingRecipe:on:", "allButFirst", "=="]}),
-smalltalk.PluggableExporter);
-
-smalltalk.addMethod(
-smalltalk.method({
-selector: "exportAll",
+selector: "exportAllPackages",
 fn: function (){
 var self=this;
 function $Smalltalk(){return smalltalk.Smalltalk||(typeof Smalltalk=="undefined"?nil:Smalltalk)}
@@ -1442,7 +2034,7 @@ return self._exportPackage_on_(pkg,stream);
 }, function($ctx3) {$ctx3.fillBlock({pkg:pkg},$ctx2)})}));
 }, function($ctx2) {$ctx2.fillBlock({stream:stream},$ctx1)})}));
 return $1;
-}, function($ctx1) {$ctx1.fill(self,"exportAll",{},smalltalk.PluggableExporter)})},
+}, function($ctx1) {$ctx1.fill(self,"exportAllPackages",{},smalltalk.PluggableExporter)})},
 messageSends: ["streamContents:", "do:", "exportPackage:on:", "packages", "current"]}),
 smalltalk.PluggableExporter);
 
@@ -1452,9 +2044,23 @@ selector: "exportPackage:on:",
 fn: function (aPackage,aStream){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-self._export_usingRecipe_on_(aPackage,self._recipe(),aStream);
+_st(self._interpreter())._interpret_for_on_(self._recipe(),aPackage,aStream);
 return self}, function($ctx1) {$ctx1.fill(self,"exportPackage:on:",{aPackage:aPackage,aStream:aStream},smalltalk.PluggableExporter)})},
-messageSends: ["export:usingRecipe:on:", "recipe"]}),
+messageSends: ["interpret:for:on:", "recipe", "interpreter"]}),
+smalltalk.PluggableExporter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "interpreter",
+fn: function (){
+var self=this;
+function $RecipeInterpreter(){return smalltalk.RecipeInterpreter||(typeof RecipeInterpreter=="undefined"?nil:RecipeInterpreter)}
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st($RecipeInterpreter())._new();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"interpreter",{},smalltalk.PluggableExporter)})},
+messageSends: ["new"]}),
 smalltalk.PluggableExporter);
 
 smalltalk.addMethod(
@@ -1484,17 +2090,17 @@ smalltalk.PluggableExporter);
 
 smalltalk.addMethod(
 smalltalk.method({
-selector: "newUsing:",
-fn: function (recipe){
+selector: "forRecipe:",
+fn: function (aRecipe){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 var $2,$3,$1;
 $2=self._new();
-_st($2)._recipe_(recipe);
+_st($2)._recipe_(aRecipe);
 $3=_st($2)._yourself();
 $1=$3;
 return $1;
-}, function($ctx1) {$ctx1.fill(self,"newUsing:",{recipe:recipe},smalltalk.PluggableExporter.klass)})},
+}, function($ctx1) {$ctx1.fill(self,"forRecipe:",{aRecipe:aRecipe},smalltalk.PluggableExporter.klass)})},
 messageSends: ["recipe:", "new", "yourself"]}),
 smalltalk.PluggableExporter.klass);
 
@@ -1511,6 +2117,85 @@ return $1;
 messageSends: ["asSet", "sortedClasses"]}),
 smalltalk.PluggableExporter.klass);
 
+
+smalltalk.addClass('RecipeInterpreter', smalltalk.Object, [], 'Importer-Exporter');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "interpret:for:on:",
+fn: function (aRecipe,anObject,aStream){
+var self=this;
+var recipeStream;
+return smalltalk.withContext(function($ctx1) { 
+recipeStream=_st(aRecipe)._readStream();
+_st((function(){
+return smalltalk.withContext(function($ctx2) {
+return _st(recipeStream)._atEnd();
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}))._whileFalse_((function(){
+return smalltalk.withContext(function($ctx2) {
+return self._interpretStep_for_on_(_st(recipeStream)._next(),anObject,aStream);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+return self}, function($ctx1) {$ctx1.fill(self,"interpret:for:on:",{aRecipe:aRecipe,anObject:anObject,aStream:aStream,recipeStream:recipeStream},smalltalk.RecipeInterpreter)})},
+messageSends: ["readStream", "whileFalse:", "interpretStep:for:on:", "next", "atEnd"]}),
+smalltalk.RecipeInterpreter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "interpretStep:for:on:",
+fn: function (aRecipeStep,anObject,aStream){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=_st(_st(aRecipeStep)._value()).__eq_eq(aRecipeStep);
+if(smalltalk.assert($1)){
+$2=self._interpretSubRecipe_for_on_(aRecipeStep,anObject,aStream);
+return $2;
+};
+_st(_st(aRecipeStep)._key())._perform_withArguments_(_st(aRecipeStep)._value(),[anObject,aStream]);
+return self}, function($ctx1) {$ctx1.fill(self,"interpretStep:for:on:",{aRecipeStep:aRecipeStep,anObject:anObject,aStream:aStream},smalltalk.RecipeInterpreter)})},
+messageSends: ["ifTrue:", "interpretSubRecipe:for:on:", "==", "value", "perform:withArguments:", "key"]}),
+smalltalk.RecipeInterpreter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "interpretSubRecipe:for:on:",
+fn: function (aRecipe,anObject,aStream){
+var self=this;
+var selection;
+return smalltalk.withContext(function($ctx1) { 
+selection=_st(_st(_st(aRecipe)._first())._key())._perform_withArguments_(_st(_st(aRecipe)._first())._value(),[anObject]);
+_st(selection)._do_((function(each){
+return smalltalk.withContext(function($ctx2) {
+return self._interpret_for_on_(_st(aRecipe)._allButFirst(),each,aStream);
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1)})}));
+return self}, function($ctx1) {$ctx1.fill(self,"interpretSubRecipe:for:on:",{aRecipe:aRecipe,anObject:anObject,aStream:aStream,selection:selection},smalltalk.RecipeInterpreter)})},
+messageSends: ["perform:withArguments:", "value", "first", "key", "do:", "interpret:for:on:", "allButFirst"]}),
+smalltalk.RecipeInterpreter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "recipe",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self["@recipe"];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"recipe",{},smalltalk.RecipeInterpreter)})},
+messageSends: []}),
+smalltalk.RecipeInterpreter);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "recipe:",
+fn: function (anArray){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@recipe"]=anArray;
+return self}, function($ctx1) {$ctx1.fill(self,"recipe:",{anArray:anArray},smalltalk.RecipeInterpreter)})},
+messageSends: []}),
+smalltalk.RecipeInterpreter);
+
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "amdNamespace",

文件差異過大導致無法顯示
+ 736 - 118
js/Importer-Exporter.js


+ 475 - 92
st/Importer-Exporter.st

@@ -42,7 +42,46 @@ amdNamesOfPackages: anArray
 		collect: [ :each | each amdNamespace, '/', each name ]
 ! !
 
-Object subclass: #ChunkExporter
+Object subclass: #AbstractExporter
+	instanceVariableNames: ''
+	package: 'Importer-Exporter'!
+!AbstractExporter commentStamp!
+I am an abstract exporter for Amber source code.!
+
+!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: 'fileOut'!
+
+recipe
+	"Recipe to export a given package."
+
+	self subclassResponsibility
+! !
+
+AbstractExporter class instanceVariableNames: 'default'!
+
+!AbstractExporter class methodsFor: 'instance creation'!
+
+default
+	^ default ifNil: [ default := self new ]
+! !
+
+AbstractExporter subclass: #ChunkExporter
 	instanceVariableNames: ''
 	package: 'Importer-Exporter'!
 !ChunkExporter commentStamp!
@@ -50,6 +89,133 @@ I am an exporter dedicated to outputting Amber source code in the classic Smallt
 
 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 match: '^\*', 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
+!
+
+methodsOfCategory: aCategory
+	"Issue #143: sort methods alphabetically"
+
+	^(aCategory methods) sorted: [:a :b | a selector <= b selector]
+!
+
+ownCategoriesOfClass: aClass
+	"Answer the protocols of aClassthat are not package extensions"
+	
+	"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 |
+		MethodCategory name: category theClass: aClass methods: (map at: category) ]
+!
+
+ownCategoriesOfMetaClass: aClass
+	"Issue #143: sort protocol alphabetically"
+
+	^self ownCategoriesOfClass: aClass class
+! !
+
+!ChunkExporter 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 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: '!!'
+!
+
+exportPackageDefinitionOf: aPackage on: aStream
+	"Chunk format."
+
+	aStream
+		nextPutAll: 'Smalltalk current createPackage: ''', aPackage name, '''!!';
+		lf
+! !
+
 !ChunkExporter class methodsFor: 'exporting-accessing'!
 
 extensionCategoriesOfPackage: package
@@ -192,70 +358,163 @@ classNameFor: aClass
 			ifFalse: [aClass name]]
 ! !
 
-Object subclass: #ChunkParser
-	instanceVariableNames: 'stream'
+AbstractExporter subclass: #Exporter
+	instanceVariableNames: ''
 	package: 'Importer-Exporter'!
-!ChunkParser commentStamp!
-I am responsible for parsing aStream contents in the chunk format.
+!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 case
+
+I am typically used to save code outside of the Amber runtime (committing to disk, etc.).
 
 ## API
 
-    ChunkParser new
-        stream: aStream;
-        nextChunk!
+Use `#exportAll`, `#exportClass:` or `#exportPackage:` methods.!
 
-!ChunkParser methodsFor: 'accessing'!
+!Exporter methodsFor: 'accessing'!
 
-stream: aStream
-	stream := aStream
-! !
+extensionMethodsOfPackage: aPackage
+	"Issue #143: sort classes and methods alphabetically"
 
-!ChunkParser methodsFor: 'reading'!
+	| name result |
+	name := aPackage 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
+!
 
-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.
+ownMethodsOfClass: aClass
+	"Issue #143: sort methods alphabetically"
 
-	This metod returns next chunk as a String (trimmed), empty String (all whitespace) or nil."
+	^((aClass methodDictionary values) sorted: [:a :b | a selector <= b selector])
+		reject: [:each | (each category match: '^\*')]
+!
 
-	| 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 !!"
+ownMethodsOfMetaClass: aClass
+	"Issue #143: sort methods alphabetically"
+
+	^self ownMethodsOfClass: aClass class
 ! !
 
-!ChunkParser class methodsFor: 'not yet classified'!
+!Exporter methodsFor: 'convenience'!
 
-on: aStream
-	^self new stream: aStream
+classNameFor: aClass
+	^aClass isMetaclass
+		ifTrue: [ aClass instanceClass name, '.klass' ]
+		ifFalse: [
+			aClass isNil
+				ifTrue: [ 'nil' ]
+				ifFalse: [ aClass name ] ]
 ! !
 
-Object subclass: #Exporter
-	instanceVariableNames: ''
-	package: 'Importer-Exporter'!
-!Exporter commentStamp!
-I am responsible for outputting Amber code into a JavaScript string.
+!Exporter methodsFor: 'fileOut'!
 
-The generated output is enough to reconstruct the exported data, including Smalltalk source code and other metadata.
+recipe
+	"Export a given package."
 
-## Use case
+	^{
+		self -> #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: }.
+		self -> #exportPackageEpilogueOf:on:
+	}
+! !
 
-I am typically used to save code outside of the Amber runtime (committing to disk, etc.).
+!Exporter methodsFor: 'output'!
 
-## API
+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
+!
 
-Use `#exportAll`, `#exportClass:` or `#exportPackage:` methods.!
+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
+!
+
+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
+! !
 
 !Exporter class methodsFor: 'exporting-accessing'!
 
@@ -415,6 +674,38 @@ 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: '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: ');'.
+	aStream 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: 'fn: ', aMethod fn compiledSource, ',';lf;
+		nextPutAll: 'messageSends: ', aMethod messageSends asJavascript;
+		nextPutAll: '}),';lf;
+		nextPutAll: 'smalltalk.', (self classNameFor: aMethod methodClass);
+		nextPutAll: ');';lf;lf
+! !
+
 !StrippedExporter class methodsFor: 'exporting-output'!
 
 exportDefinitionOf: aClass on: aStream
@@ -447,6 +738,55 @@ exportMethod: aMethod on: aStream
 		nextPutAll: ');';lf;lf
 ! !
 
+Object subclass: #ChunkParser
+	instanceVariableNames: 'stream'
+	package: 'Importer-Exporter'!
+!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 metod 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: 'not yet classified'!
+
+on: aStream
+	^self new stream: aStream
+! !
+
 Object subclass: #Importer
 	instanceVariableNames: ''
 	package: 'Importer-Exporter'!
@@ -490,8 +830,8 @@ methods
 	^methods
 !
 
-methods: anArray
-	methods := anArray
+methods: aCollection
+	methods := aCollection
 !
 
 name
@@ -535,7 +875,7 @@ commit: aPackage
 		do: [ :commitStrategyFactory || fileContents commitStrategy |
 			commitStrategy := commitStrategyFactory value: aPackage.
 			fileContents := String streamContents: [ :stream |
-				(PluggableExporter newUsing: commitStrategy key) exportPackage: aPackage on: stream ].
+				(PluggableExporter forRecipe: commitStrategy key) exportPackage: aPackage on: stream ].
 			self ajaxPutAt: commitStrategy value data: fileContents ]
 		displayingProgress: 'Committing package ', aPackage name
 !
@@ -665,9 +1005,9 @@ I should not be used directly. Instead, use the corresponding `Package` methods.
 
 commitChannels
 	^{ 
-		[ :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') ]
+		[ :pkg | Exporter default recipe -> (pkg commitPathJs, '/', pkg name, '.js') ].
+		[ :pkg | StrippedExporter default recipe -> (pkg commitPathJs, '/', pkg name, '.deploy.js') ].
+		[ :pkg | ChunkExporter default recipe -> (pkg commitPathSt, '/', pkg name, '.st') ]
 	}
 !
 
@@ -767,32 +1107,22 @@ I am an engine for exporting structured data on a Stream.
 
 My instances are created using
   PluggableExporter newUsing: recipe,
-where recipe is structured description of the exporting algorithm,
+where recipe is structured description of the exporting algorithm.
 
-Then actual exporting is done using
-  aPluggableExporter export: data usingRecipe: recipe on: stream
+The actual exporting is done by interpreting the recipe using a `RecipeInterpreter`.
 
-Recipe is an array, which can contain two kinds of elements:
- - an assocation where key is the receiver and value is two-arg selector
-    In this case, `receiver perform: selector withArguments: { data. stream }` is called.
-	This essentially defines one step of export process.
-	The key (eg. receiver) is presumed to be some kind of 'repository' of the exporting methods
-	that just format appropriate aspect of data into a stream; like a class or a singleton,
-	so you can make the recipe itself decoupled from data.
- - a subarray (sa), where first element is special and the rest is recursive recipe
-    `sa first` must be an association similar to one above,
-	with key being the 'repository' receiver, but value is one-arg selector.
-	In this case, `receiver perform: selector withArguments: { data }` should create a collection.
-	Then, the sub-recipe (`sa allButFirst`) is applied to every element of a collection, eg.
-	  collection do: [ :each | self export: each using: sa allButFirst on: stream ]
-  
-I am used to export amber packages, so I have convenience method
-exportPackage: aPackage on: aStream
-which exports aPackage using recipe you passed on newUsing:
+
+I am used to export amber packages, so I have a convenience method
+`exportPackage: aPackage on: aStream`
+which exports `aPackage` using the `recipe`
 (it is otherwise no special, so it may be renamed to export:on:)!
 
 !PluggableExporter methodsFor: 'accessing'!
 
+interpreter
+	^ RecipeInterpreter new
+!
+
 recipe
 	^recipe
 !
@@ -803,21 +1133,7 @@ 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
+exportAllPackages
 	"Export all packages in the system."
 
 	^String streamContents: [:stream |
@@ -826,14 +1142,10 @@ exportAll
 !
 
 exportPackage: aPackage on: aStream
-	self export: aPackage usingRecipe: self recipe on: aStream
+	self interpreter interpret: self recipe for: aPackage on: aStream
 ! !
 
-!PluggableExporter class methodsFor: 'exporting-accessing'!
-
-newUsing: recipe
-	^self new recipe: recipe; yourself
-!
+!PluggableExporter class methodsFor: 'convenience'!
 
 ownClassesOfPackage: package
 	"Export classes in dependency order.
@@ -841,6 +1153,77 @@ ownClassesOfPackage: package
 	^package sortedClasses asSet
 ! !
 
+!PluggableExporter class methodsFor: 'instance creation'!
+
+forRecipe: aRecipe
+	^self new recipe: aRecipe; yourself
+! !
+
+Object subclass: #RecipeInterpreter
+	instanceVariableNames: ''
+	package: 'Importer-Exporter'!
+!RecipeInterpreter commentStamp!
+I am an interpreter for export recipes.
+
+## Recipe format
+
+Recipe is an array, which can contain two kinds of elements:
+
+ - an assocation where the key is the receiver and the value is a two-arguments selector
+    In this case, `receiver perform: selector withArguments: { data. stream }` is called.
+	This essentially defines one step of export process.
+	The key (eg. receiver) is presumed to be some kind of 'repository' of the exporting methods
+	that just format appropriate aspect of data into a stream; like a class or a singleton,
+	so that the recipe itself can be decoupled from data.
+
+ - a subarray, where first element is special and the rest is recursive recipe.
+
+    `subarray first` must be an association similar to one above,
+	with key being the 'repository' receiver, but value is one-arg selector.
+	In this case, `receiver perform: selector withArguments: { data }` should create a collection.
+	Then, the sub-recipe (`subarray allButFirst`) is applied to every element of a collection, eg.
+	  collection do: [ :each | self export: each using: sa allButFirst on: stream ]!
+
+!RecipeInterpreter methodsFor: 'accessing'!
+
+recipe
+	^recipe
+!
+
+recipe: anArray
+	recipe := anArray
+! !
+
+!RecipeInterpreter methodsFor: 'interpreting'!
+
+interpret: aRecipe for: anObject on: aStream
+	| recipeStream |
+	
+	recipeStream := aRecipe readStream.
+	
+	[ recipeStream atEnd ] whileFalse: [
+		self 
+			interpretStep: recipeStream next
+			for: anObject
+			on: aStream ]
+!
+
+interpretStep: aRecipeStep for: anObject on: aStream
+	aRecipeStep value == aRecipeStep ifTrue: [ 
+		^ self interpretSubRecipe: aRecipeStep for: anObject on: aStream ].
+			
+	aRecipeStep key perform: aRecipeStep value withArguments: { anObject. aStream }
+!
+
+interpretSubRecipe: aRecipe for: anObject on: aStream
+	| selection |
+	selection := aRecipe first key 
+		perform: aRecipe first value 
+		withArguments: { anObject }.
+	selection do: [ :each |	
+		self interpret: aRecipe allButFirst for: each on: aStream ]
+! !
+
 !Package methodsFor: '*Importer-Exporter'!
 
 amdNamespace

部分文件因文件數量過多而無法顯示