David T. Lewis uploaded a new version of MonticelloConfigurations to project The Trunk:
http://source.squeak.org/trunk/MonticelloConfigurations-dtl.161.mcz
==================== Summary ====================
Name: MonticelloConfigurations-dtl.161
Author: dtl
Time: 9 May 2020, 10:22:36.276105 am
UUID: 67fc7184-897f-417f-ab90-5f241d35e26b
Ancestors: MonticelloConfigurations-mt.160
A MCConfigurationExtended is a configuration with author initials, timestamp, UUID identifier, comment, and a list of prior versions. Its external storage format is organized for compatibility with MCConfiguration, such that an image wtih support for only MCConfiguration can use configurations saved from a MCConfigurationExtended.
The intended use is to enable documentation of configuration maps, and to allow modifications to a configuration map without loss of version history.
When editing an MCConfiguration, a copyForEdit of the configuration is modfied, leaving the prior configuration in the version history. When saving an edited version, an editor window allows a version comment to be entered for the new configuration. Version history for a saved MCConfiguration is trimmed to a maximum of 10 prior versions to maintain reasonable storage size. Full version history can be reconstructed based on the UUID identifiers.
MCConfigurationBrowser provides a "Versions" button to open an explorer on the version history of a configuration. No other support for browsing version history and comments is provided.
MCConfigurationExtended is fully backward compatible such that saved versions will be rendered as simple MCConfiguration without version history in an image that lacks support for the extended format.
A SqueakSource server must have this update applied before it can render a saved MCConfigurationExtended.
=============== Diff against MonticelloConfigurations-mt.160 ===============
Item was added:
+ ----- Method: MCConfiguration class>>concreteClassFor: (in category 'private') -----
+ concreteClassFor: configArray
+ ^ (configArray includes: #mcmVersion)
+ ifTrue: [MCConfigurationExtended]
+ ifFalse: [MCConfiguration].
+
+ !
Item was added:
+ ----- Method: MCConfiguration class>>copyWithoutKeyPrefix: (in category 'private') -----
+ copyWithoutKeyPrefix: configArray
+ "Tokens in the version history portion of configArray are prefixed with $X to
+ prevent them being parsed in the original implementation of MCConfiguration.
+ Here we remove the prefixes prior to processing in the current implementation
+ with MCConfigurationExtended support. See #contentsOn:keyPrefix: for the
+ prefix writer."
+ | strm |
+ strm := #() writeStream.
+ configArray do: [ :token |
+ token caseOf: {
+ [#Xname ] -> [ strm nextPut: #name] .
+ [#Xrepository ] -> [ strm nextPut: #repository] .
+ [#Xdependency ] -> [ strm nextPut: #dependency] .
+ [#XmcmVersion] -> [ strm nextPut: #mcmVersion] .
+ [#Xid] -> [ strm nextPut: #id] .
+ [#XauthorInitials ] -> [ strm nextPut: #authorInitials] .
+ [#XtimeStamp ] -> [ strm nextPut: #timeStamp] .
+ [#Xcomment ] -> [ strm nextPut: #comment]
+ }
+ otherwise: [ strm nextPut: token]
+
+
+ ].
+ ^ strm contents.
+
+ !
Item was changed:
----- Method: MCConfiguration class>>fromArray: (in category 'instance creation') -----
fromArray: anArray
+ | array |
+ array := self copyWithoutKeyPrefix: anArray.
+ ^ (self versionsFromStream: array readStream) first.
+ !
- | configuration |
- configuration := self new.
- anArray pairsDo: [:key :value |
- key = #repository
- ifTrue: [configuration repositories add: (self repositoryFromArray: value)].
- key = #dependency
- ifTrue: [configuration dependencies add: (self dependencyFromArray: value)].
- key = #name
- ifTrue: [configuration name: value].
- ].
- ^configuration!
Item was added:
+ ----- Method: MCConfiguration class>>nextArrayFrom: (in category 'private') -----
+ nextArrayFrom: configStream
+ "Each config array starts with #name. The appearance of another token of
+ that value indicates the beginning of a new configuration map for a prior
+ version of the configuration."
+ | oc |
+ oc := OrderedCollection new.
+ oc add: configStream next.
+ [configStream atEnd not and: [#name ~= configStream peek]]
+ whileTrue: [oc add: configStream next].
+ ^ oc
+ !
Item was added:
+ ----- Method: MCConfiguration class>>nextFrom: (in category 'private') -----
+ nextFrom: configStream
+
+ | configArray configuration |
+ configArray := self nextArrayFrom: configStream.
+ configuration := (self concreteClassFor: configArray) new.
+ configArray pairsDo: [:key :value |
+ configuration initializeFromKey: key value: value].
+ ^ configuration.
+ !
Item was added:
+ ----- Method: MCConfiguration class>>oldVersionOfFromArray: (in category 'private') -----
+ oldVersionOfFromArray: anArray
+ "For verifying backward compatability. This is the implementation
+ of #fromArray: prior to introduction of MCConfigurationExtended."
+ | configuration |
+ configuration := self new.
+ anArray pairsDo: [:key :value |
+ key = #repository
+ ifTrue: [configuration repositories add: (self repositoryFromArray: value)].
+ key = #dependency
+ ifTrue: [configuration dependencies add: (self dependencyFromArray: value)].
+ key = #name
+ ifTrue: [configuration name: value].
+ ].
+ ^configuration!
Item was added:
+ ----- Method: MCConfiguration class>>versionsFromStream: (in category 'private') -----
+ versionsFromStream: arrayStream
+ "Answer all versions with history list populated in each version."
+ | configuration history |
+ arrayStream atEnd ifTrue: [ ^ #() ].
+ configuration := self nextFrom: arrayStream.
+ history := self versionsFromStream: arrayStream.
+ history do: [ :ver | configuration addPriorVersion: ver ].
+ ^ { configuration }, history.
+ !
Item was added:
+ ----- Method: MCConfiguration>>= (in category 'comparing') -----
+ = configuration
+ ^ ((configuration class = self class
+ and: [configuration name = name])
+ and: [configuration dependencies = dependencies])
+ and: [configuration repositories = repositories]!
Item was added:
+ ----- Method: MCConfiguration>>addPriorVersion: (in category 'initialize') -----
+ addPriorVersion: mcConfig
+ "Do nothing, the original MCConfiguration format does not maintain history"!
Item was changed:
----- Method: MCConfiguration>>browse (in category 'actions') -----
browse
| browser |
+ browser := MCConfigurationBrowser new configuration: self copyForEdit.
- browser := MCConfigurationBrowser new configuration: self.
name ifNotNil: [:nm | browser label: browser defaultLabel , ' ' , nm].
browser show!
Item was added:
+ ----- Method: MCConfiguration>>contentsOn: (in category 'printing') -----
+ contentsOn: aStream
+ self contentsOn: aStream keyPrefix: ''.
+ !
Item was added:
+ ----- Method: MCConfiguration>>contentsOn:keyPrefix: (in category 'printing') -----
+ contentsOn: aStream keyPrefix: prefix
+ "Prepend prefix to key values. If the prefix is a non-empty string, the resulting
+ key values will be ignored when parsing an original format MCConfiguration
+ from an extended format MCM file. This provides backward compatibility for
+ older images that need to read newer format MCM files."
+
+ name ifNotNil: [:n |
+ aStream cr.
+ aStream nextPutAll: prefix,'name '.
+ aStream print: n].
+
+ repositories do: [:ea |
+ aStream cr.
+ aStream nextPutAll: prefix,'repository '.
+ (MCConfiguration repositoryToArray: ea) printElementsOn: aStream].
+
+ dependencies do: [:ea |
+ aStream cr.
+ aStream nextPutAll: prefix,'dependency '.
+ (MCConfiguration dependencyToArray: ea) printElementsOn: aStream].
+ !
Item was added:
+ ----- Method: MCConfiguration>>copyForEdit (in category 'copying') -----
+ copyForEdit
+ "Preparing to edit a configuration. Answer a new copy with the original
+ instance saved in version history, and with no author initials or timestamp.
+ The initials and timestamp are to be set immediately prior to saving an edited
+ version."
+ | config |
+ config := MCConfigurationExtended new.
+ config name: name copy.
+ config dependencies: dependencies copy.
+ config repositories: repositories copy.
+ config priorVersions addFirst: self.
+ ^ config!
Item was added:
+ ----- Method: MCConfiguration>>copyWithoutHistory (in category 'copying') -----
+ copyWithoutHistory
+ ^ self copy
+ !
Item was changed:
----- Method: MCConfiguration>>fileOutOn: (in category 'printing') -----
fileOutOn: aStream
+ self fileOutOn: aStream keyPrefix: ''
+ !
- self writerClass fileOut: self on: aStream!
Item was added:
+ ----- Method: MCConfiguration>>fileOutOn:keyPrefix: (in category 'printing') -----
+ fileOutOn: aStream keyPrefix: prefix
+
+ aStream nextPut: $(.
+ self contentsOn: aStream keyPrefix: prefix.
+ aStream cr.
+ aStream nextPut: $).
+ !
Item was added:
+ ----- Method: MCConfiguration>>hash (in category 'comparing') -----
+ hash
+ ^ (name hash bitXor: (dependencies hash)) bitXor: repositories hash
+ !
Item was added:
+ ----- Method: MCConfiguration>>initializeFromKey:value: (in category 'initialize') -----
+ initializeFromKey: key value: value
+ key = #repository
+ ifTrue: [self repositories add: (MCConfiguration repositoryFromArray: value)].
+ key = #dependency
+ ifTrue: [self dependencies add: (MCConfiguration dependencyFromArray: value)].
+ key = #name
+ ifTrue: [self name: value].
+ !
Item was changed:
MCTool subclass: #MCConfigurationBrowser
+ instanceVariableNames: 'configuration dependencyIndex repositoryIndex activeEditWindow'
- instanceVariableNames: 'configuration dependencyIndex repositoryIndex'
classVariableNames: ''
poolDictionaries: ''
category: 'MonticelloConfigurations'!
!MCConfigurationBrowser commentStamp: 'dtl 5/10/2010 21:48' prior: 0!
A MCConfigurationBrowser displays an MCConfiguration, and edits the configuration to add or remove package dependencies and repository specifications. It allows a configuration to be stored in a repository or posted to an update stream.!
Item was added:
+ ----- Method: MCConfigurationBrowser>>activeEditWindow (in category 'morphic ui') -----
+ activeEditWindow
+ ^activeEditWindow
+ !
Item was added:
+ ----- Method: MCConfigurationBrowser>>activeEditWindow: (in category 'morphic ui') -----
+ activeEditWindow: editWindow
+ "Set temporarily during the process of editing a version comment."
+ activeEditWindow ifNotNil: [:window | window delete].
+ activeEditWindow := editWindow.
+ !
Item was changed:
----- Method: MCConfigurationBrowser>>buttonSpecs (in category 'morphic ui') -----
buttonSpecs
^ #(('Add' addDependency 'Add a dependency')
('Update' updateMenu 'Update dependencies')
('Install' installMenu 'Load/Merge/Upgrade into image')
('Up' up 'Move item up in list' canMoveUp)
('Down' down 'Move item down in list' canMoveDown)
('Remove' remove 'Remove item' canRemove)
('Save' store 'Store the configuration to a repository')
+ ('Versions' versions 'Show prior versions of this configuration')
)!
Item was added:
+ ----- Method: MCConfigurationBrowser>>completeStoreAction (in category 'actions') -----
+ completeStoreAction
+ "The store method will arrange for this to be called after the user has entered
+ a comment for the configuration version being stored."
+ self activeEditWindow: nil. "Close the editor window"
+ self pickRepository
+ ifNotNil: [:repo |
+ configuration authorInitials: Utilities authorInitials.
+ configuration timeStamp: (DateAndTime fromSeconds: DateAndTime now asSeconds) printString.
+ configuration id: UUID new asString.
+ repo storeVersion: configuration.
+ self inform: 'Saved ', configuration name]!
Item was changed:
----- Method: MCConfigurationBrowser>>defaultExtent (in category 'morphic ui') -----
defaultExtent
+ ^ 450@500!
- ^ 360@500!
Item was added:
+ ----- Method: MCConfigurationBrowser>>enterVersionCommentAndCompleteWith:nameForRestore: (in category 'morphic ui') -----
+ enterVersionCommentAndCompleteWith: aConfigBrowser nameForRestore: originalName
+ "Open an editor for comment entry. When text is accepted from the editor, ask
+ if editing is done. If complete, then proceed to save the MCConfiguration. If cancelled,
+ close the edit window and do nothing further. Otherwise leave the edit window open
+ to allow further edits before proceeding to save the configuration."
+ | editWindow |
+ editWindow := UIManager default
+ edit: configuration comment
+ label: 'Enter or edit a comment for ', configuration name
+ accept: [:aText | | editingComplete |
+ editingComplete := UIManager default
+ confirm: 'Comment accepted' translated
+ title: 'Comment for ' translated, configuration name
+ trueChoice: 'Proceed to save configuration' translated
+ falseChoice: 'Continue editing comment' translated.
+ editingComplete
+ ifNil: [ "cancel button pressed"
+ configuration name: originalName. "cancelling, undo the changed name"
+ Project current
+ addDeferredUIMessage: [aConfigBrowser activeEditWindow ifNotNil: [ :win | win delete ]]]
+ ifNotNil: [ editingComplete
+ ifTrue: [configuration comment: aText asString.
+ Project current
+ addDeferredUIMessage: [aConfigBrowser completeStoreAction]]
+ ifFalse: [ "edit window remains open" ]]].
+ aConfigBrowser activeEditWindow: editWindow.
+ !
Item was changed:
----- Method: MCConfigurationBrowser>>store (in category 'actions') -----
store
+ self activeEditWindow: nil. "Close previous if still open"
(self checkRepositories and: [self checkDependencies]) ifFalse: [^self].
+ self pickName ifNotNil: [:name | | originalName |
+ originalName := configuration name.
+ configuration name: name.
+ self enterVersionCommentAndCompleteWith: self nameForRestore: originalName ].!
- self pickName ifNotNil: [:name |
- self configuration name: name.
- self pickRepository ifNotNil: [:repo |
- repo storeVersion: self configuration]].!
Item was added:
+ ----- Method: MCConfigurationBrowser>>versions (in category 'actions') -----
+ versions
+ configuration priorVersions explore!
Item was added:
+ MCConfiguration subclass: #MCConfigurationExtended
+ instanceVariableNames: 'mcmVersion id authorInitials timeStamp comment priorVersions'
+ classVariableNames: 'HistoryLimit'
+ poolDictionaries: ''
+ category: 'MonticelloConfigurations'!
+
+ !MCConfigurationExtended commentStamp: 'dtl 4/13/2020 13:57' prior: 0!
+ A MCConfigurationExtended is a configuration with author initials, timestamp, comment, and a list of prior versions. Its external storage format is organized for compatibility with MCConfiguration, such that an image wtih support for only MCConfiguration can use configurations saved from a MCConfigurationExtended. The intended use is to enable documentation of configuration maps, and to allow modifications to a configuration map without loss of version history.!
Item was added:
+ ----- Method: MCConfigurationExtended class>>initialize (in category 'class initialization') -----
+ initialize
+
+ "Limit the number of prior versions in the history list to prevent MCM files from
+ becoming unnecessarily large over time. Versions are idenitified by a UUID
+ identifier, which should be sufficient for building a full version history if needed."
+ HistoryLimit := 10.!
Item was added:
+ ----- Method: MCConfigurationExtended>>= (in category 'comparing') -----
+ = configuration
+ ^ (((super = configuration
+ and: [configuration authorInitials = authorInitials])
+ and: [configuration timeStamp = timeStamp])
+ and: [configuration id = id])
+ and: [configuration comment = comment].
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>addPriorVersion: (in category 'initialize') -----
+ addPriorVersion: mcConfig
+ priorVersions add: mcConfig!
Item was added:
+ ----- Method: MCConfigurationExtended>>authorInitials (in category 'accessing') -----
+ authorInitials
+ ^ authorInitials
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>authorInitials: (in category 'accessing') -----
+ authorInitials: initials
+ authorInitials := initials
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>comment (in category 'accessing') -----
+ comment
+ ^ comment
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>comment: (in category 'accessing') -----
+ comment: aString
+ comment := aString
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>contentsOn:keyPrefix: (in category 'printing') -----
+ contentsOn: aStream keyPrefix: prefix
+
+ super contentsOn: aStream keyPrefix: prefix.
+
+ mcmVersion ifNotNil: [:ver |
+ aStream cr.
+ aStream nextPutAll: prefix,'mcmVersion '.
+ aStream print: ver].
+
+ id ifNotNil: [:uuid |
+ aStream cr.
+ aStream nextPutAll: prefix,'id '.
+ aStream print: uuid].
+
+ authorInitials ifNotNil: [:initials |
+ aStream cr.
+ aStream nextPutAll: prefix,'authorInitials '.
+ aStream print: initials].
+
+ timeStamp ifNotNil: [:ts |
+ aStream cr.
+ aStream nextPutAll: prefix,'timeStamp '.
+ aStream print: ts].
+
+ comment ifNotNil: [:c |
+ aStream cr.
+ aStream nextPutAll: prefix,'comment '.
+ aStream print: c].
+
+ "Keys in the prior versions have a prefix to prevent them being parsed
+ into a MCConfiguration when an image that does not contain support for the
+ newer MCConfigurationExtended format. This allows older images to read
+ an MCM file with extended format and version history, treating it as if it
+ were data for the original MCConfiguration. See #copyWithoutKeyPrefix:
+ for removal of the prefix during parsing."
+ priorVersions do: [:e | e copyWithoutHistory contentsOn: aStream keyPrefix: 'X'].
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>copyForEdit (in category 'copying') -----
+ copyForEdit
+ "Preparing to edit a configuration. Answer a new copy with the original
+ instance saved in version history, and with no author initials or timestamp.
+ The initials and timestamp are to be set immediately prior to saving an edited
+ version."
+ | config |
+ config := super copyForEdit.
+ config priorVersions: priorVersions copy.
+ config priorVersions addFirst: self.
+ config authorInitials: nil.
+ config timeStamp: nil.
+ config comment: self comment copy.
+ config trimVersionList.
+ ^ config!
Item was added:
+ ----- Method: MCConfigurationExtended>>copyWithoutHistory (in category 'copying') -----
+ copyWithoutHistory
+ "When a configuration is part of a version history, do not repeatedly
+ export its history."
+
+ | config |
+ config := self copy.
+ config priorVersions: OrderedCollection new.
+ ^ config!
Item was added:
+ ----- Method: MCConfigurationExtended>>hash (in category 'comparing') -----
+ hash
+ ^ (super hash bitOr: timeStamp hash) bitXor: id.
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>id (in category 'accessing') -----
+ id
+ ^ id!
Item was added:
+ ----- Method: MCConfigurationExtended>>id: (in category 'accessing') -----
+ id: uuid
+ id := uuid!
Item was added:
+ ----- Method: MCConfigurationExtended>>initialize (in category 'initialize') -----
+ initialize
+ super initialize.
+ mcmVersion := '2'.
+ priorVersions := OrderedCollection new.!
Item was added:
+ ----- Method: MCConfigurationExtended>>initializeFromKey:value: (in category 'initialize') -----
+ initializeFromKey: key value: value
+ super initializeFromKey: key value: value.
+ key = #mcmVersion
+ ifTrue: [mcmVersion := value].
+ key = #id
+ ifTrue: [id := value].
+ key = #authorInitials
+ ifTrue: [authorInitials := value].
+ key = #timeStamp
+ ifTrue: [timeStamp := value].
+ key = #comment
+ ifTrue: [comment := value].
+
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>mcmVersion (in category 'accessing') -----
+ mcmVersion
+ ^ mcmVersion
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>printOn: (in category 'printing') -----
+ printOn: aStream
+ super printOn: aStream.
+ aStream nextPutAll: ' ', name asString, ' ', timeStamp asString, ' (', id asString, ')'.!
Item was added:
+ ----- Method: MCConfigurationExtended>>priorVersions (in category 'accessing') -----
+ priorVersions
+ ^ priorVersions
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>priorVersions: (in category 'accessing') -----
+ priorVersions: collection
+ priorVersions := collection
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>timeStamp (in category 'accessing') -----
+ timeStamp
+ ^ timeStamp
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>timeStamp: (in category 'accessing') -----
+ timeStamp: aString
+ timeStamp := aString
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>trimVersionList (in category 'initialize') -----
+ trimVersionList
+ [priorVersions size > HistoryLimit]
+ whileTrue: [priorVersions removeLast].
+ !
Item was added:
+ ----- Method: MCConfigurationExtended>>versions (in category 'initialize') -----
+ versions
+ "myself with all prior versions"
+ ^ { self } , priorVersions.
+ !
Item was changed:
----- Method: MCMcmWriter>>writeConfiguration: (in category 'writing') -----
writeConfiguration: aConfiguration
+ aConfiguration fileOutOn: stream.
-
- stream nextPut: $(.
-
- aConfiguration name ifNotNil: [:n |
- stream cr.
- stream nextPutAll: 'name '.
- stream print: n].
-
- aConfiguration repositories do: [:ea |
- stream cr.
- stream nextPutAll: 'repository '.
- (MCConfiguration repositoryToArray: ea) printElementsOn: stream].
-
- aConfiguration dependencies do: [:ea |
- stream cr.
- stream nextPutAll: 'dependency '.
- (MCConfiguration dependencyToArray: ea) printElementsOn: stream].
-
- stream cr.
- stream nextPut: $).
!
Nicolas Cellier uploaded a new version of Compiler to project The Trunk:
http://source.squeak.org/trunk/Compiler-nice.428.mcz
==================== Summary ====================
Name: Compiler-nice.428
Author: nice
Time: 9 May 2020, 12:24:17.077079 am
UUID: 04b649b2-1b1b-486b-aa82-15b219803431
Ancestors: Compiler-nice.427
Use the idea from Compiler-ct.423: define selectFrom:to:during: in Parser for handling temporary change of text selection - see method comment.
=============== Diff against Compiler-nice.427 ===============
Item was changed:
----- Method: Parser>>ambiguousSelector:inRange: (in category 'error correction') -----
ambiguousSelector: aString inRange: anInterval
+ | correctedSelector intervalWithOffset |
- | correctedSelector userSelection intervalWithOffset |
self interactive ifFalse: [
"In non interactive mode, compile with backward comapatibility: $- is part of literal argument"
Transcript cr; store: encoder classEncoding; nextPutAll:#'>>';store: encoder selector; show: ' would send ' , token , '-'.
^super ambiguousSelector: aString inRange: anInterval].
"handle the text selection"
- userSelection := cue requestor selectionInterval.
intervalWithOffset := anInterval first + requestorOffset to: anInterval last + requestorOffset.
+ self selectFrom: intervalWithOffset first to: intervalWithOffset last
+ during:
+ ["Build the menu with alternatives"
+ correctedSelector := AmbiguousSelector
+ signalName: aString
+ inRange: intervalWithOffset.
+ correctedSelector ifNil: [^self fail]].
- cue requestor selectFrom: intervalWithOffset first to: intervalWithOffset last.
-
- "Build the menu with alternatives"
- correctedSelector := AmbiguousSelector
- signalName: aString
- inRange: intervalWithOffset.
- correctedSelector ifNil: [^self fail].
-
- "Restore the user selection state, but do not display selection yet
- This will avoid flashing effect when chaining multiple corrections."
- cue requestor selectIntervalInvisibly: userSelection.
"Execute the selected action"
self substituteWord: correctedSelector wordInterval: intervalWithOffset offset: 0.
token := (correctedSelector readStream upTo: Character space) asSymbol!
Item was changed:
----- Method: Parser>>correctSelector:wordIntervals:exprInterval:ifAbort: (in category 'error correction') -----
correctSelector: proposedKeyword wordIntervals: spots exprInterval: expInt ifAbort: abortAction
"Correct the proposedKeyword to some selector symbol, correcting the original text if such action is indicated. abortAction is invoked if the proposedKeyword couldn't be converted into a valid selector. Spots is an ordered collection of intervals within the test stream of the for each of the keyword parts."
+ | correctSelector |
- | correctSelector userSelection |
"If we can't ask the user, assume that the keyword will be defined later"
+ self interactive ifFalse: [^ proposedKeyword asSymbol].
+
+ self selectFrom: spots first first to: spots last last during: [
+ correctSelector := UnknownSelector name: proposedKeyword.
+ correctSelector ifNil: [^ abortAction value]].
+
- self interactive ifFalse: [^proposedKeyword asSymbol].
-
- userSelection := cue requestor selectionInterval.
- cue requestor selectFrom: spots first first to: spots last last.
-
- correctSelector := UnknownSelector name: proposedKeyword.
- correctSelector ifNil: [^abortAction value].
-
- "Restore the user selection state, but do not display selection yet
- This will avoid flashing effect when chaining multiple corrections."
- cue requestor selectIntervalInvisibly: userSelection.
-
self substituteSelector: correctSelector keywords wordIntervals: spots.
+ ^ (proposedKeyword last ~~ $:
- ^(proposedKeyword last ~~ $:
and: [correctSelector last == $:])
ifTrue: [abortAction value]
ifFalse: [correctSelector]!
Item was changed:
----- Method: Parser>>correctVariable:interval: (in category 'error correction') -----
correctVariable: proposedVariable interval: spot
"Correct the proposedVariable to a known variable, or declare it as a new
variable if such action is requested. We support declaring lowercase
variables as temps or inst-vars, and uppercase variables as Globals or
ClassVars, depending on whether the context is nil (class=UndefinedObject).
+ Spot is the interval within the test stream of the variable."
- Spot is the interval within the test stream of the variable.
- rr 3/4/2004 10:26 : adds the option to define a new class. "
+ | binding action |
"Check if this is an i-var, that has been corrected already (ugly)"
-
- "Display the pop-up menu"
-
- | binding userSelection action |
(encoder classEncoding instVarNames includes: proposedVariable) ifTrue:
[^InstanceVariableNode new
name: proposedVariable
index: (encoder classEncoding allInstVarNames indexOf: proposedVariable)].
"First check to see if the requestor knows anything about the variable"
(binding := cue requestor ifNotNil: [:object | object bindingOf: proposedVariable])
ifNotNil: [^encoder global: binding name: proposedVariable].
"If we can't ask the user for correction, make it undeclared"
self interactive ifFalse: [^encoder undeclared: proposedVariable].
+ self selectFrom: spot first to: spot last
+ during:
+ ["Build the menu with alternatives"
+ action := UndeclaredVariable
+ signalFor: self
+ name: proposedVariable
+ inRange: spot.
+ action ifNil: [^self fail]].
- userSelection := cue requestor selectionInterval.
- cue requestor selectFrom: spot first to: spot last.
- "Build the menu with alternatives"
- action := UndeclaredVariable
- signalFor: self
- name: proposedVariable
- inRange: spot.
- action ifNil: [^self fail].
-
- "Restore the user selection state, but do not display selection yet
- This will avoid flashing effect when chaining multiple corrections."
- cue requestor selectIntervalInvisibly: userSelection.
-
"Execute the selected action"
^action value!
Item was changed:
----- Method: Parser>>declareUndeclaredTemps: (in category 'error correction') -----
declareUndeclaredTemps: methodNode
"Declare any undeclared temps, declaring them at the smallest enclosing scope."
+ | undeclared blocksToVars |
- | undeclared userSelection blocksToVars |
(undeclared := encoder undeclaredTemps) isEmpty ifTrue:
[^self].
- userSelection := cue requestor selectionInterval.
blocksToVars := IdentityDictionary new.
undeclared do:
[:var|
(blocksToVars
at: (var tag == #method
ifTrue: [methodNode block]
ifFalse: [methodNode accept: (VariableScopeFinder new ofVariable: var)])
ifAbsentPut: [SortedCollection new]) add: var name].
(blocksToVars removeKey: methodNode block ifAbsent: []) ifNotNil:
[:rootVars|
rootVars do: [:varName| self pasteTempAtMethodLevel: varName]].
(blocksToVars keys sorted: [:a :b| a tempsMark < b tempsMark]) do:
[:block| | decl |
decl := (blocksToVars at: block) reduce: [:a :b| a, ' ', b].
block temporaries isEmpty
ifTrue:
[self substituteWord: ' | ', decl, ' |'
wordInterval: (block tempsMark + 1 to: block tempsMark)
offset: requestorOffset]
ifFalse:
[self substituteWord: decl, ' '
wordInterval: (block tempsMark to: block tempsMark - 1)
offset: requestorOffset]].
- cue requestor selectInvisiblyFrom: userSelection first to: userSelection last + requestorOffset.
ReparseAfterSourceEditing signal!
Item was changed:
----- Method: Parser>>queryUndefined (in category 'error correction') -----
queryUndefined
| varStart varName |
varName := parseNode key.
varStart := self endOfLastToken + requestorOffset - varName size + 1.
+ self selectFrom: varStart to: varStart + varName size - 1 during: [
+ (UndefinedVariable name: varName) ifFalse: [^ self fail]].!
- cue requestor selectFrom: varStart to: varStart + varName size - 1.
- (UndefinedVariable name: varName) ifFalse: [^ self fail]!
Item was added:
+ ----- Method: Parser>>selectFrom:to:during: (in category 'error correction') -----
+ selectFrom: start to: stop during: aBlock
+ "Temporarily focus user attention on a zone of error thru text section.
+ Then restore original user selection.
+ Note: the original selection is restored invisibly (not displayed).
+ This will avoid flickering when chaining multiple corrections."
+
+ | userSelection |
+ userSelection := cue requestor selectionInterval.
+ cue requestor selectFrom: start to: stop.
+ aBlock value.
+ cue requestor selectIntervalInvisibly: userSelection!
Nicolas Cellier uploaded a new version of Regex-Core to project The Trunk:
http://source.squeak.org/trunk/Regex-Core-ct.56.mcz
==================== Summary ====================
Name: Regex-Core-ct.56
Author: ct
Time: 6 March 2020, 8:32:34.316886 pm
UUID: 22955049-895d-ca43-8919-1d9f44b851f9
Ancestors: Regex-Core-ct.55
Implements lookbehinds (both positive and negative) in Regular Expressions for Squeak.
'(?<=(?<!n''t\s+)l[o]ve\s+)\w+' asRegex matchesIn: 'I love Squeak. I don''t love C++. I love Smalltalk.'.
- Honor lookbehind syntax in RxParser (see #lookAround)
- Add #forward argument to nodes/links messages. Extend RxsLookaround state by #forward boolean.
- Create RxmLookahead as universal class for both lookahead and lookbehind links. Remove RxmLookahead.
- Implement actual lookbehind logic in RxMatcher >> #matchAgainstLookbehind:positive:nextLink:.
Again, I decided not to give further support for the #forward-less versions of the relevant messages, nor for the class RxmLookahead. First, I'm not sure whether "lookahead" is a universally useful default assumption for "lookaround". Also, these selectors have been introduced just a few hours ago in Regex-Core-ct.55, so there should not be a high demand for backward compatibility. Second, all these things are hidden behind the RxMatcher facade, so foreign clients should not really depend on them. (Bad pun: Don't look-behind the facade ...) Opinions on this topic are highly appreciated.
This commit depends indeed on Regex-Core-ct.55. Please review carefully! The lookbehind matching implementation uses an intuitive algorithm. I give absolutely no guarantee that this is an efficient implementation, but for the beginning, even a superpolynomial complexity should be better than no implementation at all, shouldn't it? :-) Further information about lookbehinds can be found here: https://www.regular-expressions.info/lookaround.html
=============== Diff against Regex-Core-ct.55 ===============
Item was added:
+ ----- Method: RxMatchOptimizer>>syntaxLookaround:forward:positive: (in category 'double dispatch') -----
+ syntaxLookaround: lookaroundNode forward: forward positive: positive
+ "Do nothing."!
Item was removed:
- ----- Method: RxMatchOptimizer>>syntaxLookaround:positive: (in category 'double dispatch') -----
- syntaxLookaround: lookaroundNode positive: positive
- "Do nothing."!
Item was added:
+ ----- Method: RxMatcher>>matchAgainstLookbehind:positive:nextLink: (in category 'matching') -----
+ matchAgainstLookbehind: lookbehind positive: positive nextLink: anRmxLink
+
+ | position matchesLookbehind |
+ position := stream position.
+ matchesLookbehind := (position to: 0 by: -1)
+ anySatisfy: [:index |
+ stream position: index.
+ (lookbehind matchAgainst: self)
+ and: [stream position = position]].
+ matchesLookbehind = positive
+ ifFalse: [^ false].
+ stream position: position.
+ ^ anRmxLink matchAgainst: self!
Item was added:
+ ----- Method: RxMatcher>>syntaxLookaround:forward:positive: (in category 'double dispatch') -----
+ syntaxLookaround: lookaroundNode forward: forwardBoolean positive: positiveBoolean
+ "Double dispatch from the syntax tree.
+ Special link can handle lookarounds (look ahead and look behind, positive and negative)."
+ | piece |
+ piece := lookaroundNode piece dispatchTo: self.
+ ^ RxmLookaround with: piece forward: forwardBoolean positive: positiveBoolean!
Item was removed:
- ----- Method: RxMatcher>>syntaxLookaround:positive: (in category 'double dispatch') -----
- syntaxLookaround: lookaroundNode positive: positiveBoolean
- "Double dispatch from the syntax tree.
- Special link can handle lookarounds (look ahead, positive and negative)."
- | piece |
- piece := lookaroundNode piece dispatchTo: self.
- ^ RxmLookahead with: piece positive: positiveBoolean!
Item was changed:
----- Method: RxParser>>lookAround (in category 'recursive descent') -----
lookAround
"Parse a lookaround expression after: (?<lookaround>)
<lookaround> ::= !!<regex> | =<regex>"
+ | lookbehind positive |
+ ('!!=<' includes: lookahead) ifFalse: [
- | positive |
- ('!!=' includes: lookahead) ifFalse: [
^ self signalParseError: 'Invalid lookaround expression ?', lookahead asString].
+ lookbehind := lookahead == $<
+ ifTrue: [self next];
+ yourself.
positive := lookahead == $=.
self next.
^ RxsLookaround
with: self regex
+ forward: lookbehind not
positive: positive!
Item was removed:
- RxmLink subclass: #RxmLookahead
- instanceVariableNames: 'lookahead positive'
- classVariableNames: ''
- poolDictionaries: ''
- category: 'Regex-Core'!
-
- !RxmLookahead commentStamp: 'ct 3/6/2020 18:29' prior: 0!
- Instance holds onto a lookahead which matches but does not consume anything.
-
- Instance Variables
- lookahead: <RxmLink>
- positive: <Boolean>
- !
Item was removed:
- ----- Method: RxmLookahead class>>with:positive: (in category 'instance creation') -----
- with: aPiece positive: aBoolean
-
- ^self new lookahead: aPiece positive: aBoolean!
Item was removed:
- ----- Method: RxmLookahead>>lookahead:positive: (in category 'accessing') -----
- lookahead: anRxmLink positive: aBoolean
- lookahead := anRxmLink.
- positive := aBoolean.!
Item was removed:
- ----- Method: RxmLookahead>>matchAgainst: (in category 'matching') -----
- matchAgainst: aMatcher
- "Match if the predicate block evaluates to true when given the
- current stream character as the argument."
-
- ^aMatcher matchAgainstLookahead: lookahead positive: positive nextLink: next!
Item was removed:
- ----- Method: RxmLookahead>>postCopy (in category 'copying') -----
- postCopy
-
- super postCopy.
- lookahead := lookahead copy!
Item was removed:
- ----- Method: RxmLookahead>>postCopyUsing: (in category 'copying') -----
- postCopyUsing: anIdentityDictionary
-
- super postCopyUsing: anIdentityDictionary.
- lookahead := lookahead copyUsing: anIdentityDictionary!
Item was removed:
- ----- Method: RxmLookahead>>terminateWith: (in category 'building') -----
- terminateWith: aNode
- lookahead terminateWith: aNode.
- super terminateWith: aNode.!
Item was added:
+ RxmLink subclass: #RxmLookaround
+ instanceVariableNames: 'forward positive lookaround'
+ classVariableNames: ''
+ poolDictionaries: ''
+ category: 'Regex-Core'!
+
+ !RxmLookaround commentStamp: 'ct 3/6/2020 19:45' prior: 0!
+ Instance holds onto a lookaround which matches but does not consume anything.
+
+ Instance Variables
+ lookbehind: <RxmLink>
+ forward: <Boolean>
+ positive: <Boolean>!
Item was added:
+ ----- Method: RxmLookaround class>>with:forward:positive: (in category 'instance creation') -----
+ with: aPiece forward: forwardBoolean positive: positiveBoolean
+
+ ^self new lookaround: aPiece forward: forwardBoolean positive: positiveBoolean!
Item was added:
+ ----- Method: RxmLookaround>>lookaround:forward:positive: (in category 'accessing') -----
+ lookaround: anRxmLink forward: forwardBoolean positive: positiveBoolean
+ lookaround := anRxmLink.
+ forward := forwardBoolean.
+ positive := positiveBoolean.!
Item was added:
+ ----- Method: RxmLookaround>>matchAgainst: (in category 'matching') -----
+ matchAgainst: aMatcher
+ "Match if the predicate block evaluates to true when given the current stream character as the argument."
+
+ ^ forward
+ ifTrue: [aMatcher matchAgainstLookahead: lookaround positive: positive nextLink: next]
+ ifFalse: [aMatcher matchAgainstLookbehind: lookaround positive: positive nextLink: next]!
Item was added:
+ ----- Method: RxmLookaround>>postCopy (in category 'copying') -----
+ postCopy
+
+ super postCopy.
+ lookaround := lookaround copy!
Item was added:
+ ----- Method: RxmLookaround>>postCopyUsing: (in category 'copying') -----
+ postCopyUsing: anIdentityDictionary
+
+ super postCopyUsing: anIdentityDictionary.
+ lookaround := lookaround copyUsing: anIdentityDictionary!
Item was added:
+ ----- Method: RxmLookaround>>terminateWith: (in category 'building') -----
+ terminateWith: aNode
+ lookaround terminateWith: aNode.
+ super terminateWith: aNode.!
Item was changed:
RxsNode subclass: #RxsLookaround
+ instanceVariableNames: 'piece forward positive'
- instanceVariableNames: 'piece positive'
classVariableNames: ''
poolDictionaries: ''
category: 'Regex-Core'!
+ !RxsLookaround commentStamp: 'ct 3/6/2020 18:31' prior: 0!
+ Lookaround is used for lookaheads and lookbehinds. They are used to check if the input matches a certain subexpression without consuming any characters (e.g. not advancing the match position).
- !RxsLookaround commentStamp: '<historical>' prior: 0!
- I lookaround is used for lookaheads and lookbehinds. They are used to check if the input matches a certain subexpression without consuming any characters (e.g. not advancing the match position).
Lookarounds can be positive or negative. If they are positive the condition fails if the subexpression fails, if they are negative it is inverse.!
Item was added:
+ ----- Method: RxsLookaround class>>with:forward:positive: (in category 'instance creation') -----
+ with: aRxsRegex forward: forwardBoolean positive: positiveBoolean
+ ^ self new
+ initializePiece: aRxsRegex
+ forward: forwardBoolean
+ positive: positiveBoolean!
Item was removed:
- ----- Method: RxsLookaround class>>with:positive: (in category 'instance creation') -----
- with: aRxsRegex positive: positiveBoolean
- ^ self new
- initializePiece: aRxsRegex
- positive: positiveBoolean!
Item was added:
+ ----- Method: RxsLookaround>>beLookahead (in category 'initialize-release') -----
+ beLookahead
+ forward := true!
Item was added:
+ ----- Method: RxsLookaround>>beLookbehind (in category 'initialize-release') -----
+ beLookbehind
+ forward := false!
Item was changed:
----- Method: RxsLookaround>>dispatchTo: (in category 'accessing') -----
dispatchTo: aBuilder
"Inform the matcher of the kind of the node, and it will do whatever it has to."
+ ^aBuilder syntaxLookaround: self forward: self forward positive: self positive!
- ^aBuilder syntaxLookaround: self positive: self positive!
Item was added:
+ ----- Method: RxsLookaround>>forward (in category 'accessing') -----
+ forward
+
+ ^ forward!
Item was added:
+ ----- Method: RxsLookaround>>initializePiece:forward:positive: (in category 'initialize-release') -----
+ initializePiece: anRsxPiece forward: forwardBoolean positive: positiveBoolean
+
+ piece := anRsxPiece.
+ forward := forwardBoolean.
+ positive := positiveBoolean.!
Item was removed:
- ----- Method: RxsLookaround>>initializePiece:positive: (in category 'initialize-release') -----
- initializePiece: anRsxPiece positive: positiveBoolean
-
- piece := anRsxPiece.
- positive := positiveBoolean.!
Nicolas Cellier uploaded a new version of Regex-Core to project The Trunk:
http://source.squeak.org/trunk/Regex-Core-ct.55.mcz
==================== Summary ====================
Name: Regex-Core-ct.55
Author: ct
Time: 6 March 2020, 7:08:55.997601 pm
UUID: 4f76095b-f67f-4c41-afec-d936b7dfeecb
Ancestors: Regex-Core-eem.54
Implements positive lookaheads in Regular Expressions for Squeak
There were already some stubs and a bit of documentation, but while negative lookaheads (such as 'q(?!u)' asRegex) have been working in the past, positive lookaheads (such as 'q(?=u)' asRegex) never worked before.
- Fix erroneous parsing of positive lookahead syntax (the previous implementation missed a side effect of #regex)
- Add #positive argument to construction messages for lookahead nodes/links (see RxsLookaround >> #dispatchTo: and others, these steps had actually been forgotten)*
- In RxMatcher >> #matchAgainstLookahead:positive:nextLink:, actually respect the #positive argument
- Fix typos in documentation and category names
*Note: I decided to remove but not deprecate the original construction messages for lookahead nodes/links. The cause is that IMO, the default value should never be a negative setting, which you would not expect at first glance. Also, all the link and node classes are rather an implementation detail of Regex-Core, so I think we do not need to move these methods into the Deprecated package. Please let me know if you agree with this.
Please review! Further information about lookaheads can be found here: https://www.regular-expressions.info/lookaround.html
=============== Diff against Regex-Core-eem.54 ===============
Item was removed:
- ----- Method: RxMatchOptimizer>>syntaxLookaround: (in category 'double dispatch') -----
- syntaxLookaround: lookaroundNode
- "Do nothing."!
Item was added:
+ ----- Method: RxMatchOptimizer>>syntaxLookaround:positive: (in category 'double dispatch') -----
+ syntaxLookaround: lookaroundNode positive: positive
+ "Do nothing."!
Item was removed:
- ----- Method: RxMatcher>>matchAgainstLookahead:nextLink: (in category 'matching') -----
- matchAgainstLookahead: lookahead nextLink: anRmxLink
-
- | position result |
- position := stream position.
- result := lookahead matchAgainst: self.
- stream position: position.
- result ifTrue: [ ^false ].
- ^anRmxLink matchAgainst: self!
Item was added:
+ ----- Method: RxMatcher>>matchAgainstLookahead:positive:nextLink: (in category 'matching') -----
+ matchAgainstLookahead: lookahead positive: positive nextLink: anRmxLink
+
+ | position result |
+ position := stream position.
+ result := lookahead matchAgainst: self.
+ stream position: position.
+ ^ result = positive and: [
+ anRmxLink matchAgainst: self]!
Item was removed:
- ----- Method: RxMatcher>>syntaxLookaround: (in category 'double dispatch') -----
- syntaxLookaround: lookaroundNode
- "Double dispatch from the syntax tree.
- Special link can handle lookarounds (look ahead, positive and negative)."
- | piece |
- piece := lookaroundNode piece dispatchTo: self.
- ^ RxmLookahead with: piece!
Item was added:
+ ----- Method: RxMatcher>>syntaxLookaround:positive: (in category 'double dispatch') -----
+ syntaxLookaround: lookaroundNode positive: positiveBoolean
+ "Double dispatch from the syntax tree.
+ Special link can handle lookarounds (look ahead, positive and negative)."
+ | piece |
+ piece := lookaroundNode piece dispatchTo: self.
+ ^ RxmLookahead with: piece positive: positiveBoolean!
Item was changed:
----- Method: RxParser>>lookAround (in category 'recursive descent') -----
lookAround
+ "Parse a lookaround expression after: (?<lookaround>)
+ <lookaround> ::= !!<regex> | =<regex>"
+ | positive |
+ ('!!=' includes: lookahead) ifFalse: [
+ ^ self signalParseError: 'Invalid lookaround expression ?', lookahead asString].
+ positive := lookahead == $=.
- "Parse a lookaround expression after: (?<lookround>)
- <lookround> ::= !!<regex> | =<regex>"
- | lookaround |
- (lookahead == $!!
- or: [ lookahead == $=])
- ifFalse: [ ^ self signalParseError: 'Invalid lookaround expression ?', lookahead asString ].
self next.
+ ^ RxsLookaround
+ with: self regex
+ positive: positive!
- lookaround := RxsLookaround with: self regex.
- lookahead == $!!
- ifTrue: [ lookaround beNegative ].
- ^ lookaround
- !
Item was changed:
RxmLink subclass: #RxmLookahead
+ instanceVariableNames: 'lookahead positive'
- instanceVariableNames: 'lookahead'
classVariableNames: ''
poolDictionaries: ''
category: 'Regex-Core'!
+ !RxmLookahead commentStamp: 'ct 3/6/2020 18:29' prior: 0!
+ Instance holds onto a lookahead which matches but does not consume anything.
- !RxmLookahead commentStamp: '<historical>' prior: 0!
- Instance holds onto a lookead which matches but does not consume anything.
+ Instance Variables
+ lookahead: <RxmLink>
+ positive: <Boolean>
+ !
- Instance variables:
- predicate <RxmLink>!
Item was removed:
- ----- Method: RxmLookahead class>>with: (in category 'instance creation') -----
- with: aPiece
-
- ^self new lookahead: aPiece!
Item was added:
+ ----- Method: RxmLookahead class>>with:positive: (in category 'instance creation') -----
+ with: aPiece positive: aBoolean
+
+ ^self new lookahead: aPiece positive: aBoolean!
Item was removed:
- ----- Method: RxmLookahead>>lookahead: (in category 'accessing') -----
- lookahead: anRxmLink
- lookahead := anRxmLink!
Item was added:
+ ----- Method: RxmLookahead>>lookahead:positive: (in category 'accessing') -----
+ lookahead: anRxmLink positive: aBoolean
+ lookahead := anRxmLink.
+ positive := aBoolean.!
Item was changed:
----- Method: RxmLookahead>>matchAgainst: (in category 'matching') -----
matchAgainst: aMatcher
"Match if the predicate block evaluates to true when given the
current stream character as the argument."
+ ^aMatcher matchAgainstLookahead: lookahead positive: positive nextLink: next!
- ^aMatcher matchAgainstLookahead: lookahead nextLink: next!
Item was removed:
- ----- Method: RxsLookaround class>>with: (in category 'instance creation') -----
- with: anRsxPiece
- ^ self new
- initializePiece: anRsxPiece!
Item was added:
+ ----- Method: RxsLookaround class>>with:positive: (in category 'instance creation') -----
+ with: aRxsRegex positive: positiveBoolean
+ ^ self new
+ initializePiece: aRxsRegex
+ positive: positiveBoolean!
Item was changed:
+ ----- Method: RxsLookaround>>beNegative (in category 'initialize-release') -----
- ----- Method: RxsLookaround>>beNegative (in category 'initailize-release') -----
beNegative
positive := false!
Item was changed:
+ ----- Method: RxsLookaround>>bePositive (in category 'initialize-release') -----
- ----- Method: RxsLookaround>>bePositive (in category 'initailize-release') -----
bePositive
positive := true!
Item was changed:
----- Method: RxsLookaround>>dispatchTo: (in category 'accessing') -----
dispatchTo: aBuilder
+ "Inform the matcher of the kind of the node, and it will do whatever it has to."
+ ^aBuilder syntaxLookaround: self positive: self positive!
- "Inform the matcher of the kind of the node, and it
- will do whatever it has to."
- ^aBuilder syntaxLookaround: self!
Item was added:
+ ----- Method: RxsLookaround>>initialize (in category 'initialize-release') -----
+ initialize
+
+ super initialize.
+ self bePositive.!
Item was removed:
- ----- Method: RxsLookaround>>initializePiece: (in category 'initailize-release') -----
- initializePiece: anRsxPiece
- super initialize.
- piece := anRsxPiece.!
Item was added:
+ ----- Method: RxsLookaround>>initializePiece:positive: (in category 'initialize-release') -----
+ initializePiece: anRsxPiece positive: positiveBoolean
+
+ piece := anRsxPiece.
+ positive := positiveBoolean.!
Item was added:
+ ----- Method: RxsLookaround>>positive (in category 'accessing') -----
+ positive
+
+ ^ positive!