You can use the negative lookahead parser. I do not remember the API in Smalltalk, but in Dart (a direct port of the original Smalltalk implementation) you would write:
In Dart for comparison
final grammar = char('(')
& number
& char(',').not('please remove the extra comma from your input')
& char(')');
Original PetitParser in Smalltalk (as Lukas Renggli suggested in comments section)
To implement it in the original PetitParser (as suggested in comments):
Add message
instance variable to the class definition:
PPDelegateParser subclass:#PPNotParser
instanceVariableNames:'message'
classVariableNames:''
poolDictionaries:''
category:'PetitParser-Parsers'
In the PPNotParser>>parseOn:
parseOn: aPPContext
| element memento |
memento := aPPContext remember.
element := parser parseOn: aPPContext.
aPPContext restore: memento.
^ element isPetitFailure
ifFalse: [ PPFailure message: message context: aPPContext ]
Testing with PetitParser2
To do just testing in Pharo Smalltalk with PetitParser2 you have to do the following - to implement it as in PetitParser above would be probably harder:
- Add
#message
to #PP2NotNode
:
PP2DelegateNode subclass: #PP2NotNode
instanceVariableNames: 'message'
classVariableNames: ''
package: 'PetitParser2-Nodes'
- Create setters and getters in
#PP2NotNode
message: aString
message := aString
message
^ message
- Edit
PP2NotNode>>#parseOn:
and make decision based on if the message is empty or not:
parseOn: aPP2Context
^ self message isEmptyOrNil
ifTrue: [ strategy parseOn: aPP2Context ]
ifFalse: [ strategy parseOn: aPP2Context message: self message ]
- Next create a new method PP2Not>>parseOn:message:
parseOn: context message: aMessageString
| memento retval |
memento := self remember: context.
retval := node child parseOn: context.
self restore: context from: memento.
^ retval isPetit2Failure
ifTrue: [ nil ]
ifFalse: [ PP2Failure message: aMessageString context: context ]
- You have to enhance
PP2CompositeNodeTest>>parse: production:to:end:checkResult
:
parse: aString production: production to: expectedResult end: end checkResult: aBoolean
| ctx |
ctx := self context.
resultContext := self
parse: aString
withParser: production
withContext: ctx.
result := resultContext value.
resultContext isPetit2Failure
ifTrue: [ self unableToParse: resultContext withString: aString ].
self assert: resultContext position equals: end.
aBoolean
ifTrue: [ self assert: expectedResult equals: result ].
^ result
- Last but not the least create a method
PP2CompositeNodeTest>>unableToParse:withString:
unableToParse: aResultContext withString: aString
aResultContext message isEmptyOrNil
ifTrue: [ self deny: aResultContext isPetit2Failure description: 'Unable to parse ' , aString printString ]
ifFalse: [ self
deny: aResultContext isPetit2Failure
description:
aResultContext message , ' While parsing: ' , aString printString , '; ' , ' See postion: '
, aResultContext position asString ]
This returns your error message if the number is followed by a comma, but otherwise lets the next parser deal with the rest in the sequence.