I know there has been an old question How can a PetitParser parse rule signal an error?. Lukas Renggli has written it is:
in general this is not good style (mixes syntactic and semantic analysis)
Here is my example:
I want to parse the following term:
(0.53,00)
^
I want to tell the user the error is where the ^
points. Something like: "please remove the extra comma from your input".
The grammar is:
^ openParenthesis, number, closeParenthesis
The tokens:
openParenthesis
^ $( asPParser
number
^ wholeNumber plus, dot optional, wholeNumber optional
closeParenthesis
^ $) asPParser
wholeNumber
^#digit asPParser plus trim
How would you write a code to detect such (additional comma) kind of error?
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:
final grammar = char('(')
& number
& char(',').not('please remove the extra comma from your input')
& char(')');
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 ]
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:
#message
to #PP2NotNode
: PP2DelegateNode subclass: #PP2NotNode
instanceVariableNames: 'message'
classVariableNames: ''
package: 'PetitParser2-Nodes'
#PP2NotNode
message: aString
message := aString
message
^ message
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 ]
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 ]
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
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.