compiler/PPCNode.st
author Jan Vrany <jan.vrany@fit.cvut.cz>
Thu, 30 Jul 2015 08:37:37 +0100
changeset 510 869853decf31
parent 503 ff58cd9f1f3c
child 516 3b81c9e53352
permissions -rw-r--r--
Tests refactoring - use generated test cases to make sure all posibilities are tested. Do not generate resource for all combinations, use PPCSetUpBeforeTearDownAfterResource instead that delegates parser compilation to the testcase itself (it calls it's #setUpBefore method).

"{ Package: 'stx:goodies/petitparser/compiler' }"

"{ NameSpace: Smalltalk }"

Object subclass:#PPCNode
	instanceVariableNames:'contextFree name properties'
	classVariableNames:''
	poolDictionaries:''
	category:'PetitCompiler-Nodes'
!


!PPCNode class methodsFor:'as yet unclassified'!

new
    ^ self basicNew initialize
! !

!PPCNode methodsFor:'PetitParser mimicry'!

allParsersDo: aBlock
    "I need to mimic PetitParser protocol in order to get transformed from PPParser to PPCNode"
    ^ self allNodesDo: aBlock
!

allParsersDo: aBlock seen: aSet
    "I need to mimic PetitParser protocol in order to get transformed from PPParser to PPCNode"
    ^ self allNodesDo: aBlock seen: aSet
! !

!PPCNode methodsFor:'accessing'!

children
    ^ #()
!

firstFollowCache
    ^ self propertyAt: #firstFollowCache ifAbsentPut: [ IdentityDictionary new ]
!

firstFollowCache: value
    self propertyAt: #firstFollowCache put: value
!

markForGuard
    "Marks receiver for guards, i.e., it's guard code
     should be part of the generated code"
    self propertyAt: #guard put: true
!

markForInline
    "Marks receiver for inlining, i.e., it's parsing code
     should be inlined into parent's code"
    self propertyAt: #inlined put: true

    "Created: / 23-04-2015 / 15:39:01 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

name
    ^ name
!

name: anObject
    
    name := anObject
!

parser
    ^ self propertyAt: #parser ifAbsent: [ nil ]
!

parser: value
    self propertyAt: #parser put: value
!

prefix
    ^ 'node' 
!

suffix
    ^ self isMarkedForInline ifTrue: [ '_inlined' ] ifFalse: [ '' ]
!

unmarkForGuard
    "Forbids compiling of guards, if guards would be available"
    self propertyAt: #guard put: false

    "Created: / 23-04-2015 / 15:39:39 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

unmarkForInline
    "Forbids inlining of receiver's parsing code"
    self propertyAt: #inlined put: false

    "Created: / 23-04-2015 / 15:39:39 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!PPCNode methodsFor:'accessing-properties'!

hasProperty: aKey
    "Test if the property aKey is present."
    
    ^ properties notNil and: [ properties includesKey: aKey ]
!

properties
    ^ properties
!

properties: aDictionary
    properties := aDictionary 
!

propertyAt: aKey 
    ^ self propertyAt: aKey  ifAbsent: [ nil ]
!

propertyAt: aKey ifAbsent: aBlock
    "Answer the property value associated with aKey or, if aKey isn't found, answer the result of evaluating aBlock."
    
    ^ properties isNil
        ifTrue: [ aBlock value ]
        ifFalse: [ properties at: aKey ifAbsent: aBlock ]
!

propertyAt: aKey ifAbsentPut: aBlock
    "Answer the property associated with aKey or, if aKey isn't found store the result of evaluating aBlock as new value."
    
    ^ self propertyAt: aKey ifAbsent: [ self propertyAt: aKey put: aBlock value ]
!

propertyAt: aKey put: anObject
    "Set the property at aKey to be anObject. If aKey is not found, create a new entry for aKey and set is value to anObject. Answer anObject."

    ^ (properties ifNil: [ properties := Dictionary new: 1 ])
        at: aKey put: anObject
! !

!PPCNode methodsFor:'analysis'!

acceptsEpsilon
    "return true, if parser can accept epsilon without failure"
    ^ self subclassResponsibility
!

acceptsEpsilonOpenSet: set
    "private helper for acceptsEmpsilon that makes sure to avoid cycles (using open set)"
    self children isEmpty ifTrue: [ ^ self acceptsEpsilon ].
    
    self shouldBeImplemented .
!

allNodes
    | result |
    result := OrderedCollection new.
    self allParsersDo: [ :parser | result add: parser ].
    ^ result
!

allNodesDo: aBlock
    "Iterate over all the parse nodes of the receiver."

    self allNodesDo: aBlock seen: IdentitySet new
!

allNodesDo: aBlock seen: aSet
    "Iterate over all the parse nodes of the receiver, do not visit and follow the ones contained in aSet."

    (aSet includes: self) ifTrue: [ ^ self ].
    
    aSet add: self.
    aBlock value: self.
    self children do: [ :each | 
        each allNodesDo: aBlock seen: aSet 
    ]
!

check
    "nothing to do"
    ^ nil
!

checkTree
    | message |
    self allNodes do: [ :node | (message := node check) ifNotNil: [ self error: message ]  ].
!

firstCharSetCached
    ^ self propertyAt: #firstCharSet ifAbsentPut: [ self firstCharSet ].
!

hasFiniteLanguage
    ^ self recognizedSentences isEmpty not
!

isContextFree
    ^ contextFree ifNil: [ contextFree := self allNodes allSatisfy: [ :n | n isContextFreePrim  ] ]
!

isContextFreePrim
    ^ true
!

isFirstSetTerminal
    "Answer true if the receiver is a terminal or leaf parser, that means it does not delegate to any other parser."

    ^ self isTerminal
!

isNullable
    "Answer true if the receiver is a nullable parser, e.g. it can successfully parse nothing."
    
    ^ false
!

isTerminal
    "Answer true if the receiver is a terminal or leaf parser, that means it does not delegate to any other parser."

    ^ self children isEmpty
!

overlapsWith: anotherNode
    | finite infinite |
    "infinite - infinite"
    (self hasFiniteLanguage not and: [ anotherNode hasFiniteLanguage not ]) ifTrue: [ 
        ^ false
    ].
    
    "finite - finite"
    (self hasFiniteLanguage and: [ anotherNode hasFiniteLanguage ]) ifTrue: [ 
        | union cnt|
        cnt := self recognizedSentences size + anotherNode recognizedSentences size.
        
        union :=  Set new
                addAll: self recognizedSentences;
                addAll: anotherNode recognizedSentences;
                yourself.
        ^ (union size = cnt) not.
    ].

    self hasFiniteLanguage ifTrue: [ 
        finite := self.
        infinite := anotherNode.
    ] ifFalse: [ 
 								finite := anotherNode.
        infinite := self.	
    ].
        
    finite recognizedSentences do: [ :sentence |
            (infinite parser matches: sentence) ifTrue: [ ^ true ].
    ].
    ^ false
    
!

recognizedSentences
    ^ self propertyAt: #recognizedSentences ifAbsentPut: [ self recognizedSentencesPrim ].
!

recognizedSentencesPrim
    ^ #()
! !

!PPCNode methodsFor:'comparing'!

= anotherNode
    (self == anotherNode) ifTrue: [ ^ true ].
    (anotherNode class = self class) ifFalse: [ ^ false ].
    
    (anotherNode name = name) ifFalse: [ ^ false ].
    (anotherNode isMarkedForInline  = self isMarkedForInline) ifFalse: [ ^ false ].
    ^ anotherNode children = self children.
!

hash
    "TODO JK: IMO not a good hashing function bacause of children size, 
        but at least it is not recursive, which would be worse :)
    "
    ^ self class hash bitXor: (name hash bitXor: (self isMarkedForInline hash bitXor: (self children size hash)))
! !

!PPCNode methodsFor:'copying'!

postCopy
    super postCopy.
    properties := properties copy
! !

!PPCNode methodsFor:'first follow next'!

firstSet
    ^ self firstFollowCache  at: #firstSet ifAbsentPut: [
        self firstSets at: self
    ]
!

firstSet: set
    self firstFollowCache  at: #firstSet put: set
!

firstSetSuchThat: block
    ^ (self firstSetsSuchThat: block) at: self
!

firstSetWithProductions
    ^ self firstFollowCache  at: #firstSetWithProductions ifAbsentPut: [ 
        self firstSetSuchThat: [:e | e name isNil not ].
    ]
!

firstSetWithProductions: aSet
    ^ self firstFollowCache  at: #firstSetWithProductions put: aSet
!

firstSetWithTokens
    ^ self firstFollowCache  at: #firstSetWithTokens ifAbsentPut: [ 
        self firstSetSuchThat: [:e | e isTerminal or: [ e isTokenNode ] ].
    ]
!

firstSetWithTokens: aSet
    ^ self firstFollowCache  at: #firstSetWithTokens put: aSet
!

firstSets
    ^ self firstSetsSuchThat: [ :e | e isFirstSetTerminal ]
!

firstSets: aFirstDictionary into: aSet suchThat: aBlock
    "PRIVATE: Try to add additional elements to the first-set aSet of the receiver, use the incomplete aFirstDictionary."

    (aBlock value: self) ifFalse: [ 
        self children do: [ :node | aSet addAll: (aFirstDictionary at: node) ]
    ]
!

firstSetsSuchThat: block

    | firstSets |
    firstSets := IdentityDictionary new.
    self allNodesDo: [ :each |
        firstSets at: each put: ((block value: each)
            ifTrue: [ IdentitySet with: each ]
            ifFalse: [ IdentitySet new ]).
        each isNullable
            ifTrue: [ (firstSets at: each) add: PPCSentinelNode instance ] ].
        
        
    [	| changed tally |
        changed := false.
        firstSets keysAndValuesDo: [ :node :first |
            tally := first size.
            node firstSets: firstSets into: first suchThat: block.
            changed := changed or: [ tally ~= first size ] ].
        changed ] whileTrue.
    ^ firstSets
!

followSet
    ^ self firstFollowCache at: #followSet ifAbsent: [ 
        self error: 'no follow set cached' 
    ]
!

followSet: aSet
    ^ self firstFollowCache at: #followSet put: aSet
!

followSetIn: rootNode
    ^ rootNode followSets at: self
!

followSetWithTokens
    ^ self firstFollowCache  at: #followSetWithTokens ifAbsent: [ 
        self error: 'no follow with tokens cached' 
    ]
!

followSetWithTokens: aSet
    ^ self firstFollowCache  at: #followSetWithTokens put: aSet
!

followSets
    ^ self followSetsSuchThat: [ :e | e isFirstSetTerminal ]
!

followSets: aFollowDictionary firstSets: aFirstDictionary into: aSet suchThat: aBlock
    "PRIVATE: Try to add additional elements to the follow-set aSet of the receiver, use the incomplete aFollowDictionary and the complete aFirstDictionary."
    
    self children do: [ :node | (aFollowDictionary at: node) addAll: aSet ]
!

followSetsSuchThat: aBlock
    "Answer a dictionary with all the parsers reachable from the receiver as key and their follow-set as value. The follow-set of a parser is the list of terminal parsers that can appear immediately to the right of that parser."
    
    | current previous continue firstSets followSets |
    current := previous := 0.
    firstSets := self firstSetsSuchThat: aBlock.
    followSets := IdentityDictionary new.
    self allNodesDo: [ :each | followSets at: each put: IdentitySet new ].
    (followSets at: self) add: PPCSentinelNode instance.
    [	followSets keysAndValuesDo: [ :node :follow |
            node 
                followSets: followSets
                firstSets: firstSets
                into: follow
                suchThat: aBlock ].
        current := followSets
            inject: 0
            into: [ :result :each | result + each size ].
        continue := previous < current.
        previous := current.
        continue ] whileTrue.
    ^ followSets
!

nextSetIn: rootNode
    ^ rootNode nextSets at: self
!

nextSets
        | nextSets |
    
    nextSets := IdentityDictionary new.
    self allNodesDo: [ :each | nextSets at: each put: IdentitySet new ].
    
    (nextSets at: self) add: PPCSentinelNode instance.
    
    [ 	| changed |
        changed := false.
    
        nextSets keysAndValuesDo: [:node :next |
            changed := (node 
                nextSets: nextSets
                into: next) or: [ changed ].
        ].
        changed ] whileTrue.
    
    ^ nextSets
!

nextSets: aNextDictionary into: aSet
    "return true/false, if something has changed or not...."
    | childSet change tally |
    
    change := false.
    
    self children do: [:each | 
        childSet := aNextDictionary at: each.
        tally := childSet size.
        childSet addAll: aSet.
        change := change or: [ tally ~= childSet size ].
    ].

    ^ change
    
! !

!PPCNode methodsFor:'gt'!

gtTreeViewIn: composite
    <gtInspectorPresentationOrder: 40>

    composite tree
            title: 'Tree';
            children: [:n | n children ];
            format: [:n| String streamContents: [:s | n printOn: s. n printHashOn: s] ];
            shouldExpandToLevel: 6
! !

!PPCNode methodsFor:'inspecting'!

inspector2TabTree
    <inspector2Tab>

    ^ (self newInspector2Tab)
        label:'Tree';
        priority:50;
        view: [
            | list view |

            list := PluggableHierarchicalList new.
            list childBlock: [ :parent | parent children ].
            list labelBlock: [ :child | child printString ].
            list root: self.
            view := ScrollableView for:HierarchicalListView.
            view useDefaultIcons: false.
            view list: list.
            view
        ];
        yourself

    "Modified: / 22-05-2015 / 17:05:02 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (comment): / 18-06-2015 / 06:04:41 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!PPCNode methodsFor:'printing'!

printHashOn: aStream
    aStream print: 'Hash:', self hash asString
!

printIdOn: aStream
    aStream print: 'ID:', self identityHash asString
!

printNameOn: aStream
    self name isNil
        ifFalse: [ aStream nextPutAll: self name. aStream nextPut: $-.  ].
        
    aStream print: self identityHash
!

printOn: aStream
    super printOn: aStream.
    aStream nextPut: $(.
    self printNameOn: aStream.
    self isMarkedForInline ifTrue:[ 
        aStream nextPutAll: ' INL'
    ].
    aStream nextPut: $)

    "Modified: / 22-05-2015 / 15:34:50 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!PPCNode methodsFor:'testing'!

canHavePPCId
    ^ true
!

isMarkedForInline
    ^ self propertyAt: #inlined ifAbsent: [ false ].

    "Created: / 23-04-2015 / 15:40:10 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

isSequenceNode
    ^ false

    "Created: / 15-06-2015 / 18:29:32 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

isTokenNode
    ^ false
!

isTrimmingTokenNode
    ^ false
! !

!PPCNode methodsFor:'transformation'!

asCompilerNode
    ^ self
!

asFsa
    | visitor |
    visitor := PEGFsaGenerator new.
    ^ visitor visit: self
!

replace: node with: anotherNode
!

transform: aBlock
    "Answer a copy of all parsers reachable from the receiver transformed using aBlock."
    | mapping root |
    mapping := IdentityDictionary new.
    self allNodesDo: [ :each |
        mapping
            at: each
            put: (aBlock value: each copy) ].
    root := mapping at: self.
    [	| changed |
        changed := false.
        root allNodesDo: [ :each |
            each children do: [ :old |
                mapping at: old ifPresent: [ :new |
                    each replace: old with: new.
                    changed := true ] ] ].
        changed ] whileTrue.
    ^ root
! !

!PPCNode methodsFor:'visiting'!

accept: visitor
    visitor visitNode: self
! !

!PPCNode class methodsFor:'documentation'!

version_HG

    ^ '$Changeset: <not expanded> $'
! !