1

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)

My question is how to do it with more complex example correctly?

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?

1 Answer 1

1

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.

3
  • Thank you very much for you answer. I'll adjust it to smalltalk (I don't have a clue about Dart) edit your answer with the adjusted code. Is the negative lookahead parser the only way? Because if the example gets even more complicated it can get complex. I can ask different question for that.
    – tukan
    Commented Jul 4 at 8:12
  • Hmm the method #not is defined as ^ PP2NotNode on: self how do I send the message? And #not: is not there.
    – tukan
    Commented Jul 4 at 8:31
  • 1
    In the original PetitParser the error message of the PPNotParser is unfortunately not configurable and hardcoded to be an empty string. However, it can be easily changed by adding a message instance variable and creating the error with the contents of that variable: i.sstatic.net/6HnrePOB.png Commented Jul 4 at 11:10

Not the answer you're looking for? Browse other questions tagged or ask your own question.