Jelajahi Sumber

String >> #, with better type checks.

Fix #1189. Fix #1190. Fix #1238.
Herby Vojčík 4 tahun lalu
induk
melakukan
e5943e560e

+ 23 - 11
cli/src/AmberCli.js

@@ -770,11 +770,11 @@ selector: "handlePUTRequest:respondTo:",
 protocol: "request handling",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aRequest", "aResponse"],
-source: "handlePUTRequest: aRequest respondTo: aResponse\x0a\x09| file stream |\x0a\x09(self isAuthenticated: aRequest)\x0a\x09\x09ifFalse: [self respondAuthenticationRequiredTo: aResponse. ^ nil].\x0a\x0a\x09file := '.', aRequest url.\x0a\x09stream := fs createWriteStream: file.\x0a\x0a\x09stream on: 'error' do: [:error |\x0a\x09\x09console warn: 'Error creating WriteStream for file ', file.\x0a\x09\x09console warn: '    Did you forget to create the necessary directory in your project (often /src)?'.\x0a\x09\x09console warn: '    The exact error is: ', error.\x0a\x09\x09self respondNotCreatedTo: aResponse].\x0a\x0a\x09stream on: 'close' do: [\x0a\x09\x09self respondCreatedTo: aResponse].\x0a\x0a\x09aRequest setEncoding: 'utf8'.\x0a\x09aRequest on: 'data' do: [:data |\x0a\x09\x09stream write: data].\x0a\x0a\x09aRequest on: 'end' do: [\x0a\x09\x09stream writable ifTrue: [stream end]]",
+source: "handlePUTRequest: aRequest respondTo: aResponse\x0a\x09| file stream |\x0a\x09(self isAuthenticated: aRequest)\x0a\x09\x09ifFalse: [self respondAuthenticationRequiredTo: aResponse. ^ nil].\x0a\x0a\x09file := '.', aRequest url.\x0a\x09stream := fs createWriteStream: file.\x0a\x0a\x09stream on: 'error' do: [:error |\x0a\x09\x09console warn: 'Error creating WriteStream for file ', file.\x0a\x09\x09console warn: '    Did you forget to create the necessary directory in your project (often /src)?'.\x0a\x09\x09console warn: '    The exact error is: ', error asString.\x0a\x09\x09self respondNotCreatedTo: aResponse].\x0a\x0a\x09stream on: 'close' do: [\x0a\x09\x09self respondCreatedTo: aResponse].\x0a\x0a\x09aRequest setEncoding: 'utf8'.\x0a\x09aRequest on: 'data' do: [:data |\x0a\x09\x09stream write: data].\x0a\x0a\x09aRequest on: 'end' do: [\x0a\x09\x09stream writable ifTrue: [stream end]]",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["ifFalse:", "isAuthenticated:", "respondAuthenticationRequiredTo:", ",", "url", "createWriteStream:", "on:do:", "warn:", "respondNotCreatedTo:", "respondCreatedTo:", "setEncoding:", "write:", "ifTrue:", "writable", "end"]
+messageSends: ["ifFalse:", "isAuthenticated:", "respondAuthenticationRequiredTo:", ",", "url", "createWriteStream:", "on:do:", "warn:", "asString", "respondNotCreatedTo:", "respondCreatedTo:", "setEncoding:", "write:", "ifTrue:", "writable", "end"]
 }, function ($methodClass){ return function (aRequest,aResponse){
 var self=this,$self=this;
 var file,stream;
@@ -809,7 +809,7 @@ return $core.withContext(function($ctx2) {
 ,$ctx2.sendIdx["warn:"]=2
 //>>excludeEnd("ctx");
 ][0];
-$recv(console)._warn_("    The exact error is: ".__comma(error));
+$recv(console)._warn_("    The exact error is: ".__comma($recv(error)._asString()));
 return $self._respondNotCreatedTo_(aResponse);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx2) {$ctx2.fillBlock({error:error},$ctx1,2)});
@@ -1501,11 +1501,11 @@ selector: "start",
 protocol: "starting",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "start\x0a\x09\x22Checks if required directory layout is present (issue warning if not).\x0a\x09 Afterwards start the server.\x22\x0a\x09self checkDirectoryLayout.\x0a\x09(http createServer: [:request :response |\x0a\x09      self handleRequest: request respondTo: response])\x0a\x09      on: 'error' do: [:error | console log: 'Error starting server: ', error];\x0a\x09      on: 'listening' do: [console log: 'Starting file server on http://', self host, ':', self port asString];\x0a\x09      listen: self port host: self host.",
+source: "start\x0a\x09\x22Checks if required directory layout is present (issue warning if not).\x0a\x09 Afterwards start the server.\x22\x0a\x09self checkDirectoryLayout.\x0a\x09(http createServer: [:request :response |\x0a\x09      self handleRequest: request respondTo: response])\x0a\x09      on: 'error' do: [:error | console log: 'Error starting server: ', error asString];\x0a\x09      on: 'listening' do: [console log: 'Starting file server on http://', self host, ':', self port asString];\x0a\x09      listen: self port host: self host.",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["checkDirectoryLayout", "on:do:", "createServer:", "handleRequest:respondTo:", "log:", ",", "host", "asString", "port", "listen:host:"]
+messageSends: ["checkDirectoryLayout", "on:do:", "createServer:", "handleRequest:respondTo:", "log:", ",", "asString", "host", "port", "listen:host:"]
 }, function ($methodClass){ return function (){
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
@@ -1526,7 +1526,11 @@ return $self._handleRequest_respondTo_(request,response);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx2) {
 //>>excludeEnd("ctx");
-return [$recv(console)._log_(["Error starting server: ".__comma(error)
+return [$recv(console)._log_(["Error starting server: ".__comma([$recv(error)._asString()
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx2.sendIdx["asString"]=1
+//>>excludeEnd("ctx");
+][0])
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 ,$ctx2.sendIdx[","]=1
 //>>excludeEnd("ctx");
@@ -1796,11 +1800,11 @@ selector: "createServerWithArguments:",
 protocol: "initialization",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["options"],
-source: "createServerWithArguments: options\x0a\x09\x22If options are empty return a default FileServer instance.\x0a\x09 If options are given loop through them and set the passed in values\x0a\x09 on the FileServer instance.\x0a\x09 \x0a\x09 Commanline options map directly to methods in the 'accessing' protocol\x0a\x09 taking one parameter.\x0a\x09 Adding a method to this protocol makes it directly settable through\x0a\x09 command line options.\x0a\x09 \x22\x0a\x09| server popFront front optionName optionValue switches |\x0a\x0a\x09switches := self commandLineSwitches.\x0a\x0a\x09server := self new.\x0a\x0a\x09options ifEmpty: [^server].\x0a\x0a\x09(options size even) ifFalse: [\x0a\x09\x09console log: 'Using default parameters.'.\x0a\x09\x09console log: 'Wrong commandline options or not enough arguments for: ' , options.\x0a\x09\x09console log: 'Use any of the following ones: ', switches.\x0a\x09\x09^server].\x0a\x0a\x09popFront := [:args |\x0a\x09\x09front := args first.\x0a\x09\x09args remove: front.\x0a\x09\x09front].\x0a\x0a\x09[options notEmpty] whileTrue: [\x0a\x09\x09optionName  := popFront value: options.\x0a\x09\x09optionValue := popFront value: options.\x0a\x0a\x09\x09(switches includes: optionName) ifTrue: [\x0a\x09\x09\x09optionName := self selectorForCommandLineSwitch: optionName.\x0a\x09\x09\x09server perform: optionName withArguments: { optionValue } ]\x0a\x09\x09\x09ifFalse: [\x0a\x09\x09\x09\x09console log: optionName, ' is not a valid commandline option'.\x0a\x09\x09\x09\x09console log: 'Use any of the following ones: ', switches ]].\x0a\x09^ server.",
+source: "createServerWithArguments: options\x0a\x09\x22If options are empty return a default FileServer instance.\x0a\x09 If options are given loop through them and set the passed in values\x0a\x09 on the FileServer instance.\x0a\x09 \x0a\x09 Commanline options map directly to methods in the 'accessing' protocol\x0a\x09 taking one parameter.\x0a\x09 Adding a method to this protocol makes it directly settable through\x0a\x09 command line options.\x0a\x09 \x22\x0a\x09| server popFront front optionName optionValue switches |\x0a\x0a\x09switches := self commandLineSwitches.\x0a\x0a\x09server := self new.\x0a\x0a\x09options ifEmpty: [^server].\x0a\x0a\x09(options size even) ifFalse: [\x0a\x09\x09console log: 'Using default parameters.'.\x0a\x09\x09console log: 'Wrong commandline options or not enough arguments for: ' , (' ' join: options).\x0a\x09\x09console log: 'Use any of the following ones: ', (',' join: switches).\x0a\x09\x09^server].\x0a\x0a\x09popFront := [:args |\x0a\x09\x09front := args first.\x0a\x09\x09args remove: front.\x0a\x09\x09front].\x0a\x0a\x09[options notEmpty] whileTrue: [\x0a\x09\x09optionName  := popFront value: options.\x0a\x09\x09optionValue := popFront value: options.\x0a\x0a\x09\x09(switches includes: optionName) ifTrue: [\x0a\x09\x09\x09optionName := self selectorForCommandLineSwitch: optionName.\x0a\x09\x09\x09server perform: optionName withArguments: { optionValue } ]\x0a\x09\x09\x09ifFalse: [\x0a\x09\x09\x09\x09console log: optionName, ' is not a valid commandline option'.\x0a\x09\x09\x09\x09console log: 'Use any of the following ones: ', (',' join: switches) ]].\x0a\x09^ server.",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["commandLineSwitches", "new", "ifEmpty:", "ifFalse:", "even", "size", "log:", ",", "first", "remove:", "whileTrue:", "notEmpty", "value:", "ifTrue:ifFalse:", "includes:", "selectorForCommandLineSwitch:", "perform:withArguments:"]
+messageSends: ["commandLineSwitches", "new", "ifEmpty:", "ifFalse:", "even", "size", "log:", ",", "join:", "first", "remove:", "whileTrue:", "notEmpty", "value:", "ifTrue:ifFalse:", "includes:", "selectorForCommandLineSwitch:", "perform:withArguments:"]
 }, function ($methodClass){ return function (options){
 var self=this,$self=this;
 var server,popFront,front,optionName,optionValue,switches;
@@ -1821,7 +1825,11 @@ if(!$core.assert($recv($recv(options)._size())._even())){
 ,$ctx1.sendIdx["log:"]=1
 //>>excludeEnd("ctx");
 ][0];
-[$recv(console)._log_(["Wrong commandline options or not enough arguments for: ".__comma(options)
+[$recv(console)._log_(["Wrong commandline options or not enough arguments for: ".__comma([" "._join_(options)
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx1.sendIdx["join:"]=1
+//>>excludeEnd("ctx");
+][0])
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 ,$ctx1.sendIdx[","]=1
 //>>excludeEnd("ctx");
@@ -1830,7 +1838,11 @@ if(!$core.assert($recv($recv(options)._size())._even())){
 ,$ctx1.sendIdx["log:"]=2
 //>>excludeEnd("ctx");
 ][0];
-[$recv(console)._log_(["Use any of the following ones: ".__comma(switches)
+[$recv(console)._log_(["Use any of the following ones: ".__comma([","._join_(switches)
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx1.sendIdx["join:"]=2
+//>>excludeEnd("ctx");
+][0])
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 ,$ctx1.sendIdx[","]=2
 //>>excludeEnd("ctx");
@@ -1883,7 +1895,7 @@ return $recv(server)._perform_withArguments_(optionName,[optionValue]);
 ,$ctx2.sendIdx["log:"]=4
 //>>excludeEnd("ctx");
 ][0];
-return $recv(console)._log_("Use any of the following ones: ".__comma(switches));
+return $recv(console)._log_("Use any of the following ones: ".__comma(","._join_(switches)));
 }
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1,5)});

+ 5 - 5
cli/src/AmberCli.st

@@ -328,7 +328,7 @@ handlePUTRequest: aRequest respondTo: aResponse
 	stream on: 'error' do: [:error |
 		console warn: 'Error creating WriteStream for file ', file.
 		console warn: '    Did you forget to create the necessary directory in your project (often /src)?'.
-		console warn: '    The exact error is: ', error.
+		console warn: '    The exact error is: ', error asString.
 		self respondNotCreatedTo: aResponse].
 
 	stream on: 'close' do: [
@@ -437,7 +437,7 @@ start
 	self checkDirectoryLayout.
 	(http createServer: [:request :response |
 	      self handleRequest: request respondTo: response])
-	      on: 'error' do: [:error | console log: 'Error starting server: ', error];
+	      on: 'error' do: [:error | console log: 'Error starting server: ', error asString];
 	      on: 'listening' do: [console log: 'Starting file server on http://', self host, ':', self port asString];
 	      listen: self port host: self host.
 !
@@ -939,8 +939,8 @@ createServerWithArguments: options
 
 	(options size even) ifFalse: [
 		console log: 'Using default parameters.'.
-		console log: 'Wrong commandline options or not enough arguments for: ' , options.
-		console log: 'Use any of the following ones: ', switches.
+		console log: 'Wrong commandline options or not enough arguments for: ' , (' ' join: options).
+		console log: 'Use any of the following ones: ', (',' join: switches).
 		^server].
 
 	popFront := [:args |
@@ -957,7 +957,7 @@ createServerWithArguments: options
 			server perform: optionName withArguments: { optionValue } ]
 			ifFalse: [
 				console log: optionName, ' is not a valid commandline option'.
-				console log: 'Use any of the following ones: ', switches ]].
+				console log: 'Use any of the following ones: ', (',' join: switches) ]].
 	^ server.
 !
 

+ 9 - 5
lang/src/Compiler-Core.js

@@ -950,11 +950,11 @@ selector: "parseError:parsing:",
 protocol: "error handling",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["anException", "aString"],
-source: "parseError: anException parsing: aString\x0a\x09(anException basicAt: 'location')\x0a\x09\x09ifNil: [ ^ anException pass ]\x0a\x09\x09ifNotNil: [ :loc |\x0a\x09\x09\x09^ ParseError new \x0a\x09\x09\x09\x09messageText: \x0a\x09\x09\x09\x09\x09'Parse error on line ', loc start line ,\x0a\x09\x09\x09\x09\x09' column ' , loc start column ,\x0a\x09\x09\x09\x09\x09' : Unexpected character ', (anException basicAt: 'found');\x0a\x09\x09\x09\x09yourself ]",
+source: "parseError: anException parsing: aString\x0a\x09(anException basicAt: 'location')\x0a\x09\x09ifNil: [ ^ anException pass ]\x0a\x09\x09ifNotNil: [ :loc |\x0a\x09\x09\x09^ ParseError new \x0a\x09\x09\x09\x09messageText: \x0a\x09\x09\x09\x09\x09'Parse error on line ', loc start line asString,\x0a\x09\x09\x09\x09\x09' column ' , loc start column asString,\x0a\x09\x09\x09\x09\x09' : Unexpected character ', (anException basicAt: 'found');\x0a\x09\x09\x09\x09yourself ]",
 referencedClasses: ["ParseError"],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["ifNil:ifNotNil:", "basicAt:", "pass", "messageText:", "new", ",", "line", "start", "column", "yourself"]
+messageSends: ["ifNil:ifNotNil:", "basicAt:", "pass", "messageText:", "new", ",", "asString", "line", "start", "column", "yourself"]
 }, function ($methodClass){ return function (anException,aString){
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
@@ -972,15 +972,19 @@ return $recv(anException)._pass();
 var loc;
 loc=$1;
 $2=$recv($globals.ParseError)._new();
-$recv($2)._messageText_([$recv([$recv([$recv([$recv("Parse error on line ".__comma($recv([$recv(loc)._start()
+$recv($2)._messageText_([$recv([$recv([$recv([$recv("Parse error on line ".__comma([$recv($recv([$recv(loc)._start()
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 ,$ctx1.sendIdx["start"]=1
 //>>excludeEnd("ctx");
-][0])._line())).__comma(" column ")
+][0])._line())._asString()
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx1.sendIdx["asString"]=1
+//>>excludeEnd("ctx");
+][0])).__comma(" column ")
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 ,$ctx1.sendIdx[","]=4
 //>>excludeEnd("ctx");
-][0]).__comma($recv($recv(loc)._start())._column())
+][0]).__comma($recv($recv($recv(loc)._start())._column())._asString())
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 ,$ctx1.sendIdx[","]=3
 //>>excludeEnd("ctx");

+ 2 - 2
lang/src/Compiler-Core.st

@@ -291,8 +291,8 @@ parseError: anException parsing: aString
 		ifNotNil: [ :loc |
 			^ ParseError new 
 				messageText: 
-					'Parse error on line ', loc start line ,
-					' column ' , loc start column ,
+					'Parse error on line ', loc start line asString,
+					' column ' , loc start column asString,
 					' : Unexpected character ', (anException basicAt: 'found');
 				yourself ]
 ! !

+ 4 - 4
lang/src/Compiler-IR.js

@@ -3399,11 +3399,11 @@ selector: "jsOverride:args:",
 protocol: "pragmas",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aString", "aCollection"],
-source: "jsOverride: aString args: aCollection\x0a\x09| myArgs |\x0a\x09myArgs := self irMethod arguments.\x0a\x09myArgs size = aCollection size ifFalse: [\x0a\x09\x09CompilerError signal: 'Should have ', self irMethod arguments size, ' args in <jsOverride:args:>.' ].\x0a\x09myArgs asSet = aCollection asSet ifFalse: [\x0a\x09\x09CompilerError signal: 'Argument mismatch in <jsOverride:args:>.' ].\x0a\x09self irMethod attachments\x0a\x09\x09at: aString\x0a\x09\x09put: (NativeFunction\x0a\x09\x09\x09constructorNamed: #Function\x0a\x09\x09\x09value: (',' join: aCollection)\x0a\x09\x09\x09value: 'return this.', irMethod selector asJavaScriptMethodName, '(', (',' join: myArgs), ')')",
+source: "jsOverride: aString args: aCollection\x0a\x09| myArgs |\x0a\x09myArgs := self irMethod arguments.\x0a\x09myArgs size = aCollection size ifFalse: [\x0a\x09\x09CompilerError signal: 'Should have ', self irMethod arguments size asString, ' args in <jsOverride:args:>.' ].\x0a\x09myArgs asSet = aCollection asSet ifFalse: [\x0a\x09\x09CompilerError signal: 'Argument mismatch in <jsOverride:args:>.' ].\x0a\x09self irMethod attachments\x0a\x09\x09at: aString\x0a\x09\x09put: (NativeFunction\x0a\x09\x09\x09constructorNamed: #Function\x0a\x09\x09\x09value: (',' join: aCollection)\x0a\x09\x09\x09value: 'return this.', irMethod selector asJavaScriptMethodName, '(', (',' join: myArgs), ')')",
 referencedClasses: ["CompilerError", "NativeFunction"],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["arguments", "irMethod", "ifFalse:", "=", "size", "signal:", ",", "asSet", "at:put:", "attachments", "constructorNamed:value:value:", "join:", "asJavaScriptMethodName", "selector"]
+messageSends: ["arguments", "irMethod", "ifFalse:", "=", "size", "signal:", ",", "asString", "asSet", "at:put:", "attachments", "constructorNamed:value:value:", "join:", "asJavaScriptMethodName", "selector"]
 }, function ($methodClass){ return function (aString,aCollection){
 var self=this,$self=this;
 var myArgs;
@@ -3432,11 +3432,11 @@ if(!$core.assert([$recv([$recv(myArgs)._size()
 ,$ctx1.sendIdx["="]=1
 //>>excludeEnd("ctx");
 ][0])){
-[$recv($globals.CompilerError)._signal_([$recv(["Should have ".__comma($recv($recv([$self._irMethod()
+[$recv($globals.CompilerError)._signal_([$recv(["Should have ".__comma($recv($recv($recv([$self._irMethod()
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 ,$ctx1.sendIdx["irMethod"]=2
 //>>excludeEnd("ctx");
-][0])._arguments())._size())
+][0])._arguments())._size())._asString())
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 ,$ctx1.sendIdx[","]=2
 //>>excludeEnd("ctx");

+ 1 - 1
lang/src/Compiler-IR.st

@@ -894,7 +894,7 @@ jsOverride: aString args: aCollection
 	| myArgs |
 	myArgs := self irMethod arguments.
 	myArgs size = aCollection size ifFalse: [
-		CompilerError signal: 'Should have ', self irMethod arguments size, ' args in <jsOverride:args:>.' ].
+		CompilerError signal: 'Should have ', self irMethod arguments size asString, ' args in <jsOverride:args:>.' ].
 	myArgs asSet = aCollection asSet ifFalse: [
 		CompilerError signal: 'Argument mismatch in <jsOverride:args:>.' ].
 	self irMethod attachments

+ 70 - 8
lang/src/Kernel-Collections.js

@@ -1671,11 +1671,11 @@ selector: "shortenedPrintString",
 protocol: "printing",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "shortenedPrintString\x0a\x09^ self size <= 1\x0a\x09\x09ifTrue: [ self printString ]\x0a\x09\x09ifFalse: [ (self copyEmpty copyWith: self anyOne) printString, ' ... ', (self size - 1), ' more items' ]",
+source: "shortenedPrintString\x0a\x09^ self size <= 1\x0a\x09\x09ifTrue: [ self printString ]\x0a\x09\x09ifFalse: [ (self copyEmpty copyWith: self anyOne) printString, ' ... ', (self size - 1) asString, ' more items' ]",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["ifTrue:ifFalse:", "<=", "size", "printString", ",", "copyWith:", "copyEmpty", "anyOne", "-"]
+messageSends: ["ifTrue:ifFalse:", "<=", "size", "printString", ",", "copyWith:", "copyEmpty", "anyOne", "asString", "-"]
 }, function ($methodClass){ return function (){
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
@@ -1692,7 +1692,7 @@ return [$self._printString()
 //>>excludeEnd("ctx");
 ][0];
 } else {
-return [$recv([$recv($recv($recv($recv($self._copyEmpty())._copyWith_($self._anyOne()))._printString()).__comma(" ... ")).__comma($recv($self._size()).__minus((1)))
+return [$recv([$recv($recv($recv($recv($self._copyEmpty())._copyWith_($self._anyOne()))._printString()).__comma(" ... ")).__comma($recv($recv($self._size()).__minus((1)))._asString())
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 ,$ctx1.sendIdx[","]=2
 //>>excludeEnd("ctx");
@@ -2851,7 +2851,7 @@ selector: "shortenedPrintString",
 protocol: "printing",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "shortenedPrintString\x0a\x09^ self size <= 1\x0a\x09\x09ifTrue: [ self printString ]\x0a\x09\x09ifFalse: [ | key | key := self keys anyOne. (self copyEmpty at: key put: (self at: key); yourself) printString, ' ... ', (self size - 1), ' more items' ]",
+source: "shortenedPrintString\x0a\x09^ self size <= 1\x0a\x09\x09ifTrue: [ self printString ]\x0a\x09\x09ifFalse: [ | key | key := self keys anyOne. (self copyEmpty at: key put: (self at: key); yourself) printString, ' ... ', (self size - 1) size, ' more items' ]",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
@@ -2877,7 +2877,11 @@ var key;
 key=$recv($self._keys())._anyOne();
 $1=$self._copyEmpty();
 $recv($1)._at_put_(key,$self._at_(key));
-return [$recv([$recv($recv($recv($recv($1)._yourself())._printString()).__comma(" ... ")).__comma($recv($self._size()).__minus((1)))
+return [$recv([$recv($recv($recv($recv($1)._yourself())._printString()).__comma(" ... ")).__comma([$recv($recv($self._size()).__minus((1)))._size()
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx1.sendIdx["size"]=2
+//>>excludeEnd("ctx");
+][0])
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 ,$ctx1.sendIdx[","]=2
 //>>excludeEnd("ctx");
@@ -4646,6 +4650,38 @@ return x;
 }; }),
 $globals.Array);
 
+$core.addMethod(
+$core.method({
+selector: "appendToString:",
+protocol: "copying",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aString"],
+source: "appendToString: aString\x0a<inlineJS: '\x0a\x09for (var i = 0, l = $self.length; i < l; ++i) {\x0a\x09\x09var el = $self[i];\x0a\x09\x09if ((typeof el === \x22string\x22) || $recv(el)._isString()) {\x0a\x09\x09\x09if (el.length === 1) { aString += el; continue; }\x0a\x09\x09}\x0a\x09\x09$self._error_(\x22Not a character.\x22);\x0a\x09}\x0a\x09return aString'>",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [["inlineJS:", ["\x0a\x09for (var i = 0, l = $self.length; i < l; ++i) {\x0a\x09\x09var el = $self[i];\x0a\x09\x09if ((typeof el === \x22string\x22) || $recv(el)._isString()) {\x0a\x09\x09\x09if (el.length === 1) { aString += el; continue; }\x0a\x09\x09}\x0a\x09\x09$self._error_(\x22Not a character.\x22);\x0a\x09}\x0a\x09return aString"]]],
+messageSends: []
+}, function ($methodClass){ return function (aString){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+
+	for (var i = 0, l = $self.length; i < l; ++i) {
+		var el = $self[i];
+		if ((typeof el === "string") || $recv(el)._isString()) {
+			if (el.length === 1) { aString += el; continue; }
+		}
+		$self._error_("Not a character.");
+	}
+	return aString;
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"appendToString:",{aString:aString})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.Array);
+
 $core.addMethod(
 $core.method({
 selector: "asJavaScriptSource",
@@ -5315,17 +5351,19 @@ selector: ",",
 protocol: "copying",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aString"],
-source: ", aString\x0a\x09<inlineJS: 'return String(self) + aString'>",
+source: ", aString\x0a\x09<inlineJS: 'return typeof aString === \x22string\x22 ?\x0a\x09\x09String(self) + aString :\x0a\x09\x09$recv(aString)._appendToString_(String(self))'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
-pragmas: [["inlineJS:", ["return String(self) + aString"]]],
+pragmas: [["inlineJS:", ["return typeof aString === \x22string\x22 ?\x0a\x09\x09String(self) + aString :\x0a\x09\x09$recv(aString)._appendToString_(String(self))"]]],
 messageSends: []
 }, function ($methodClass){ return function (aString){
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return String(self) + aString;
+return typeof aString === "string" ?
+		String(self) + aString :
+		$recv(aString)._appendToString_(String(self));
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx1) {$ctx1.fill(self,",",{aString:aString})});
@@ -5504,6 +5542,30 @@ return self;
 }; }),
 $globals.String);
 
+$core.addMethod(
+$core.method({
+selector: "appendToString:",
+protocol: "copying",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aString"],
+source: "appendToString: aString\x0a\x09<inlineJS: 'return aString + self'>",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [["inlineJS:", ["return aString + self"]]],
+messageSends: []
+}, function ($methodClass){ return function (aString){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return aString + self;
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"appendToString:",{aString:aString})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.String);
+
 $core.addMethod(
 $core.method({
 selector: "asJavaScriptMethodName",

+ 21 - 3
lang/src/Kernel-Collections.st

@@ -379,7 +379,7 @@ errorNotFound
 shortenedPrintString
 	^ self size <= 1
 		ifTrue: [ self printString ]
-		ifFalse: [ (self copyEmpty copyWith: self anyOne) printString, ' ... ', (self size - 1), ' more items' ]
+		ifFalse: [ (self copyEmpty copyWith: self anyOne) printString, ' ... ', (self size - 1) asString, ' more items' ]
 ! !
 
 !Collection methodsFor: 'streaming'!
@@ -677,7 +677,7 @@ printOn: aStream
 shortenedPrintString
 	^ self size <= 1
 		ifTrue: [ self printString ]
-		ifFalse: [ | key | key := self keys anyOne. (self copyEmpty at: key put: (self at: key); yourself) printString, ' ... ', (self size - 1), ' more items' ]
+		ifFalse: [ | key | key := self keys anyOne. (self copyEmpty at: key put: (self at: key); yourself) printString, ' ... ', (self size - 1) size, ' more items' ]
 ! !
 
 !AssociativeCollection methodsFor: 'testing'!
@@ -1137,6 +1137,18 @@ reversed
 
 !Array methodsFor: 'copying'!
 
+appendToString: aString
+<inlineJS: '
+	for (var i = 0, l = $self.length; i < l; ++i) {
+		var el = $self[i];
+		if ((typeof el === "string") || $recv(el)._isString()) {
+			if (el.length === 1) { aString += el; continue; }
+		}
+		$self._error_("Not a character.");
+	}
+	return aString'>
+!
+
 copyFrom: anIndex to: anotherIndex
 <inlineJS: '
 	if (anIndex >= 1 && anotherIndex <= self.length) {
@@ -1415,7 +1427,13 @@ uriEncoded
 !String methodsFor: 'copying'!
 
 , aString
-	<inlineJS: 'return String(self) + aString'>
+	<inlineJS: 'return typeof aString === "string" ?
+		String(self) + aString :
+		$recv(aString)._appendToString_(String(self))'>
+!
+
+appendToString: aString
+	<inlineJS: 'return aString + self'>
 !
 
 copyFrom: anIndex to: anotherIndex

+ 24 - 0
lang/src/Kernel-Objects.js

@@ -678,6 +678,30 @@ return $recv($globals.Association)._key_value_(self,anObject);
 }; }),
 $globals.Object);
 
+$core.addMethod(
+$core.method({
+selector: "appendToString:",
+protocol: "copying",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aString"],
+source: "appendToString: aString\x0a\x09self error: 'Cannot add self to a string.'",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["error:"]
+}, function ($methodClass){ return function (aString){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+$self._error_("Cannot add self to a string.");
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"appendToString:",{aString:aString})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.Object);
+
 $core.addMethod(
 $core.method({
 selector: "asJSONString",

+ 4 - 0
lang/src/Kernel-Objects.st

@@ -242,6 +242,10 @@ asJavaScriptSource
 
 !Object methodsFor: 'copying'!
 
+appendToString: aString
+	self error: 'Cannot add self to a string.'
+!
+
 copy
 	^ self shallowCopy postCopy
 !