'From Cuis 2.3 of 22 March 2010 [latest update: #472] on 14 April 2010 at 1:42:59 pm'!

!Character methodsFor: 'testing' stamp: 'jmv 4/14/2010 11:53'!
isGenerallyValidInIdentifiers
	"Can c be part of an identifier? (unary or keyword selector, or variable name)
	Warning: Does not account for the fact that some classes could redefine 
	#allowUnderscoreSelectors"
	^self isAlphaNumeric or: [ self  = $_ and: [ Preferences allowUnderscoreSelectors ]]! !

!Character methodsFor: 'testing' stamp: 'jmv 4/14/2010 11:56'!
isGenerallyValidStartOfInIdentifiers
	"Can c be the first character of an identifier? (unary or keyword selector, or variable name)
	Warning: Does not account for the fact that some classes could redefine 
	#allowUnderscoreSelectors"
	^self isLetter or: [ self  = $_ and: [ Preferences allowUnderscoreSelectors ]]! !

!Character methodsFor: 'testing' stamp: 'jmv 4/14/2010 11:58'!
tokenish
	"Answer whether the receiver is a valid token-character--letter, digit, or 
	colon."

	^self isGenerallyValidInIdentifiers or: [self = $:]! !


!DifferenceFinder methodsFor: 'private' stamp: 'jmv 4/14/2010 13:23'!
keywordsAndBlanksFrom: aString
	^Array streamContents: [:strm | | read keyword tail |
		read := aString readStream.
		[read atEnd] whileFalse: [
			keyword := read nextKeyword.
			keyword notEmpty ifTrue: [
				strm nextPut: keyword ].
			tail := read untilAnySatisfying: [:ch | ch isGenerallyValidInIdentifiers].
			tail notEmpty ifTrue: [strm nextPut: tail]]]
! !


!Editor methodsFor: 'private' stamp: 'jmv 4/14/2010 13:23'!
nextWord: position
	| string index |
	string _ self string.
	index _ position.
	[(index between: 1 and: string size) and: [(string at: index) isGenerallyValidInIdentifiers]]
		whileTrue: [index _ index + 1].
	[(index between: 1 and: string size) and: [(string at: index) isGenerallyValidInIdentifiers not]]
		whileTrue: [index _ index + 1].
	^ index! !

!Editor methodsFor: 'private' stamp: 'jmv 4/14/2010 13:23'!
previousWord: position
	| string index |
	string _ self string.
	index _ position.
	[(index between: 1 and: string size) and: [(string at: index) isGenerallyValidInIdentifiers not]]
		whileTrue: [index _ index - 1].
	[(index between: 1 and: string size) and: [(string at: index) isGenerallyValidInIdentifiers]]
		whileTrue: [index _ index - 1].
	^ index + 1! !


!PositionableStream methodsFor: 'accessing' stamp: 'jmv 4/14/2010 13:23'!
nextKeyword
	^self
		untilAnySatisfying: [:c | (c isGenerallyValidInIdentifiers or: [c = $:]) not]! !


!Scanner class methodsFor: 'testing' stamp: 'jmv 4/14/2010 13:42'!
wellFormedInstanceVariableNameFrom: aString
	"Answer a legal instance variable name, derived from aString"

	| cleansedString |
	cleansedString _ aString select: [:ch | ch isGenerallyValidInIdentifiers].
	(cleansedString isEmpty or: [cleansedString first isGenerallyValidStartOfInIdentifiers not])
		ifTrue: [cleansedString _ 'a', cleansedString]
		ifFalse:	[cleansedString _ cleansedString withFirstCharacterDownshifted].

	[self isLegalInstVarName: cleansedString] whileFalse:
		[cleansedString _ cleansedString, 'x'].
	^ cleansedString

"Scanner wellFormedInstanceVariableNameFrom:  '234 xx\ Uml /ler42342380-4'"! !


!SequenceableCollection methodsFor: 'private' stamp: 'jmv 4/14/2010 13:25'!
copyReplaceAll: oldSubstring with: newSubstring asTokens: ifTokens
	"Answer a copy of the receiver in which all occurrences of
	oldSubstring have been replaced by newSubstring.
	ifTokens (valid for Strings only) specifies that the characters
	surrounding the recplacement must not be alphanumeric.
		Bruce Simth,  must be incremented by 1 and not 
	newSubstring if ifTokens is true.  See example below. "

	| aString startSearch currentIndex endIndex |
	(ifTokens and: [(self isKindOf: String) not])
		ifTrue: [(self isKindOf: Text) ifFalse: [
			self error: 'Token replacement only valid for Strings']].
	aString _ self.
	startSearch _ 1.
	[(currentIndex _ aString indexOfSubCollection: oldSubstring startingAt: startSearch)
			 > 0]
		whileTrue: 
		[endIndex _ currentIndex + oldSubstring size - 1.
		(ifTokens not
			or: [(currentIndex = 1
					or: [(aString at: currentIndex-1) isGenerallyValidInIdentifiers not])
				and: [endIndex = aString size
					or: [(aString at: endIndex+1) isGenerallyValidInIdentifiers not]]])
			ifTrue: [aString _ aString
					copyReplaceFrom: currentIndex
					to: endIndex
					with: newSubstring.
				startSearch _ currentIndex + newSubstring size]
			ifFalse: [
				ifTokens 
					ifTrue: [startSearch _ currentIndex + 1]
					ifFalse: [startSearch _ currentIndex + newSubstring size]]].
	^ aString

"Test case:
	'test te string' copyReplaceAll: 'te' with: 'longone' asTokens: true   "
! !


!SmalltalkEditor methodsFor: 'menu messages' stamp: 'jmv 4/14/2010 12:01'!
explain
	"Try to shed some light on what kind of entity the current selection is. 
	The selection must be a single token or construct. Insert the answer after 
	the selection. Send private messages whose names begin with 'explain' 
	that return a string if they recognize the selection, else nil."

	| string tiVars cgVars selectors delimitors numbers sorry reply symbol |
Cursor execute showWhile: [
			sorry _ '"Sorry, I can''t explain that.  Please select a single
token, construct, or special character.'.
			sorry _ sorry , (morph canDiscardEdits
							ifFalse: ['  Also, please cancel or accept."']
							ifTrue: ['"']).
			(string _ self selection asString) isEmpty
				ifTrue: [reply _ '']
				ifFalse: [
					string _ self explainScan: string.
					"Remove space, tab, cr"
					"Temps and Instance vars need only test strings that are all letters"
					(string detect: [:char | char isGenerallyValidInIdentifiers not]
						ifNone: nil) ifNil: [
							tiVars _ (self explainTemp: string)
								ifNil: [self explainInst: string]].
					(tiVars == nil and: [model respondsTo: #explainSpecial:])
						ifTrue: [tiVars _ model explainSpecial: string].
					tiVars _ tiVars
						ifNil: [ '']
						ifNotNil: [ tiVars , '\' withCRs].
					"Context, Class, Pool, and Global vars, and Selectors need 
					only test symbols"
					(Symbol hasInterned: string ifTrue: [:s | symbol _ s])
						ifTrue: [
							cgVars _ (self explainCtxt: symbol) 
								ifNil: [ (self explainClass: symbol)
									ifNil: [ self explainGlobal: symbol]].
							"See if it is a Selector (sent here or not)"
							selectors _ (self explainMySel: symbol)
								ifNil: [(self explainPartSel: string)
									ifNil: [ self explainAnySel: symbol]]]
						ifFalse: [selectors _ self explainPartSel: string].
					cgVars _ cgVars
						ifNil: [ '']
						ifNotNil: [cgVars , '\' withCRs].
					selectors _ selectors
						ifNil: [ '']
						ifNotNil: [ selectors , '\' withCRs].
					delimitors _ string size = 1
						ifTrue: ["single special characters"
							self explainChar: string]
						ifFalse: ["matched delimitors"
							self explainDelimitor: string].
					numbers _ self explainNumber: string.
					numbers ifNil: [numbers _ ''].
					delimitors ifNil: [delimitors _ ''].
					reply _ tiVars , cgVars , selectors , delimitors , numbers].
			reply size = 0 ifTrue: [reply _ sorry].
			self afterSelectionInsertAndSelect: reply]! !


!String methodsFor: 'converting' stamp: 'jmv 4/14/2010 12:02'!
asIdentifier: shouldBeCapitalized
	"Return a legal identifier, with first character in upper case if shouldBeCapitalized is true, else lower case.  This will always return a legal identifier, even for an empty string"

	| aString firstChar firstLetterPosition |
	aString _ self select: [:el | el isGenerallyValidInIdentifiers].
	firstLetterPosition _ aString findFirst: [:ch | ch isGenerallyValidStartOfInIdentifiers].
	aString _ firstLetterPosition = 0
		ifFalse:
			[aString copyFrom: firstLetterPosition to: aString size]
		ifTrue:
			['a', aString].
	firstChar _ shouldBeCapitalized ifTrue: [aString first asUppercase] ifFalse: [aString first asLowercase].

	^ firstChar asString, (aString copyFrom: 2 to: aString size)
"
'234Fred987' asIdentifier: false
'235Fred987' asIdentifier: true
'' asIdentifier: true
'()87234' asIdentifier: false
'())z>=PPve889  U >' asIdentifier: false

"! !

!String methodsFor: 'converting' stamp: 'jmv 4/14/2010 12:03'!
asLegalSelector
	| toUse |
	toUse _ ''.
	self do:
		[:char | char isGenerallyValidInIdentifiers ifTrue: [toUse _ toUse copyWith: char]].
	(self size = 0 or: [self first isGenerallyValidStartOfInIdentifiers not])
		ifTrue:		[toUse _ 'v', toUse].

	^ toUse withFirstCharacterDownshifted

"'234znak 43 ) 2' asLegalSelector"! !

!String methodsFor: 'system primitives' stamp: 'jmv 4/14/2010 13:16'!
numArgs 
	"Answer either the number of arguments that the receiver would take if considered a selector.  Answer -1 if it couldn't be a selector.  Note that currently this will answer -1 for anything begining with an uppercase letter even though the system will accept such symbols as selectors.  It is intended mostly for the assistance of spelling correction."

	| firstChar numColons excess start ix |
	self size = 0 ifTrue: [^ -1].
	firstChar _ self at: 1.
	(firstChar isGenerallyValidStartOfInIdentifiers or: [firstChar = $:]) ifTrue:
		["Fast reject if any chars are non-alphanumeric"
		(self findSubstring: '~' in: self startingAt: 1 matchTable: Tokenish) > 0 ifTrue: [^ -1].
		"Fast colon count"
		numColons _ 0.  start _ 1.
		[(ix _ self findSubstring: ':' in: self startingAt: start matchTable: CaseSensitiveOrder) > 0]
			whileTrue:
				[numColons _ numColons + 1.
				start _ ix + 1].
		numColons = 0 ifTrue: [^ 0].
		firstChar = $:
			ifTrue: [excess _ 2 "Has an initial keyword, as #:if:then:else:"]
			ifFalse: [excess _ 0].
		self last = $:
			ifTrue: [^ numColons - excess]
			ifFalse: [^ numColons - excess - 1 "Has a final keywords as #nextPut::andCR"]].
	firstChar isSpecial ifTrue:
		[self size = 1 ifTrue: [^ 1].
		2 to: self size do: [:i | (self at: i) isSpecial ifFalse: [^ -1]].
		^ 1].
	^ -1.! !


!Symbol methodsFor: 'accessing' stamp: 'jmv 4/14/2010 13:17'!
precedence
	"Answer the receiver's precedence, assuming it is a valid Smalltalk
	message selector or 0 otherwise.  The numbers are 1 for unary,
	2 for binary and 3 for keyword selectors."

	self size = 0 ifTrue: [^ 0].
	self first isGenerallyValidStartOfInIdentifiers ifFalse: [^ 2].
	self last = $: ifTrue: [^ 3].
	^ 1! !


!Symbol class methodsFor: 'access' stamp: 'jmv 4/14/2010 13:17'!
selectorsContaining: aString
	"Answer a list of selectors that contain aString within them. Case-insensitive.  Does return symbols that begin with a capital letter."

	| size selectorList ascii |

	selectorList _ OrderedCollection new.
	(size _ aString size) = 0 ifTrue: [^selectorList].

	aString size = 1 ifTrue:
		[
			ascii _ aString first asciiValue.
			ascii < 128 ifTrue: [selectorList add: (OneCharacterSymbols at: ascii+1)]
		].

	aString first isGenerallyValidStartOfInIdentifiers ifFalse:
		[
			aString size = 2 ifTrue: 
				[Symbol hasInterned: aString ifTrue:
					[:s | selectorList add: s]].
			^selectorList
		].

	selectorList _ selectorList copyFrom: 2 to: selectorList size.

	self allSymbolTablesDo: [:each |
		each size >= size ifTrue:
			[(each findSubstring: aString in: each startingAt: 1 
				matchTable: CaseInsensitiveOrder) > 0
						ifTrue: [selectorList add: each]]].

	^selectorList reject: [:each | "reject non-selectors, but keep ones that begin with an uppercase"
		each numArgs < 0 and: [each asString withFirstCharacterDownshifted numArgs < 0]].

"Symbol selectorsContaining: 'scon'"! !


!Utilities class methodsFor: 'summer97 additions' stamp: 'jmv 4/14/2010 13:42'!
wellFormedInstanceVariableNameFrom: aString
	"Answer a legal instance variable name, derived from aString"

	| cleansedString |
	cleansedString _ aString select: [:ch | ch isGenerallyValidInIdentifiers].
	(cleansedString isEmpty or: [cleansedString first isGenerallyValidStartOfInIdentifiers not])
		ifTrue: [cleansedString _ 'a', cleansedString]
		ifFalse:	[cleansedString _ cleansedString withFirstCharacterDownshifted].

	[self isLegalInstVarName: cleansedString] whileFalse:
		[cleansedString _ cleansedString, 'x'].
	^ cleansedString

"Utilities wellFormedInstanceVariableNameFrom:  '234 xx\ Uml /ler42342380-4'"! !

