// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. // Use of this file is governed by the BSD 3-clause license that // can be found in the LICENSE.txt file in the project root. package antlr import ( "fmt" "golang.org/x/exp/slices" "strconv" ) var _emptyPredictionContextHash int func init() { _emptyPredictionContextHash = murmurInit(1) _emptyPredictionContextHash = murmurFinish(_emptyPredictionContextHash, 0) } func calculateEmptyHash() int { return _emptyPredictionContextHash } const ( // BasePredictionContextEmptyReturnState represents {@code $} in an array in full context mode, $ // doesn't mean wildcard: // // $ + x = [$,x] // // Here, // // $ = EmptyReturnState BasePredictionContextEmptyReturnState = 0x7FFFFFFF ) // TODO: JI These are meant to be atomics - this does not seem to match the Java runtime here // //goland:noinspection GoUnusedGlobalVariable var ( BasePredictionContextglobalNodeCount = 1 BasePredictionContextid = BasePredictionContextglobalNodeCount ) const ( PredictionContextEmpty = iota PredictionContextSingleton PredictionContextArray ) // PredictionContext is a go idiomatic implementation of PredictionContext that does not rty to // emulate inheritance from Java, and can be used without an interface definition. An interface // is not required because no user code will ever need to implement this interface. type PredictionContext struct { cachedHash int pcType int parentCtx *PredictionContext returnState int parents []*PredictionContext returnStates []int } func NewEmptyPredictionContext() *PredictionContext { nep := &PredictionContext{} nep.cachedHash = calculateEmptyHash() nep.pcType = PredictionContextEmpty nep.returnState = BasePredictionContextEmptyReturnState return nep } func NewBaseSingletonPredictionContext(parent *PredictionContext, returnState int) *PredictionContext { pc := &PredictionContext{} pc.pcType = PredictionContextSingleton pc.returnState = returnState pc.parentCtx = parent if parent != nil { pc.cachedHash = calculateHash(parent, returnState) } else { pc.cachedHash = calculateEmptyHash() } return pc } func SingletonBasePredictionContextCreate(parent *PredictionContext, returnState int) *PredictionContext { if returnState == BasePredictionContextEmptyReturnState && parent == nil { // someone can pass in the bits of an array ctx that mean $ return BasePredictionContextEMPTY } return NewBaseSingletonPredictionContext(parent, returnState) } func NewArrayPredictionContext(parents []*PredictionContext, returnStates []int) *PredictionContext { // Parent can be nil only if full ctx mode and we make an array // from {@link //EMPTY} and non-empty. We merge {@link //EMPTY} by using // nil parent and // returnState == {@link //EmptyReturnState}. hash := murmurInit(1) for _, parent := range parents { hash = murmurUpdate(hash, parent.Hash()) } for _, returnState := range returnStates { hash = murmurUpdate(hash, returnState) } hash = murmurFinish(hash, len(parents)<<1) nec := &PredictionContext{} nec.cachedHash = hash nec.pcType = PredictionContextArray nec.parents = parents nec.returnStates = returnStates return nec } func (p *PredictionContext) Hash() int { return p.cachedHash } func (p *PredictionContext) Equals(other Collectable[*PredictionContext]) bool { switch p.pcType { case PredictionContextEmpty: otherP := other.(*PredictionContext) return other == nil || otherP == nil || otherP.isEmpty() case PredictionContextSingleton: return p.SingletonEquals(other) case PredictionContextArray: return p.ArrayEquals(other) } return false } func (p *PredictionContext) ArrayEquals(o Collectable[*PredictionContext]) bool { if o == nil { return false } other := o.(*PredictionContext) if other == nil || other.pcType != PredictionContextArray { return false } if p.cachedHash != other.Hash() { return false // can't be same if hash is different } // Must compare the actual array elements and not just the array address // return slices.Equal(p.returnStates, other.returnStates) && slices.EqualFunc(p.parents, other.parents, func(x, y *PredictionContext) bool { return x.Equals(y) }) } func (p *PredictionContext) SingletonEquals(other Collectable[*PredictionContext]) bool { if other == nil { return false } otherP := other.(*PredictionContext) if otherP == nil { return false } if p.cachedHash != otherP.Hash() { return false // Can't be same if hash is different } if p.returnState != otherP.getReturnState(0) { return false } // Both parents must be nil if one is if p.parentCtx == nil { return otherP.parentCtx == nil } return p.parentCtx.Equals(otherP.parentCtx) } func (p *PredictionContext) GetParent(i int) *PredictionContext { switch p.pcType { case PredictionContextEmpty: return nil case PredictionContextSingleton: return p.parentCtx case PredictionContextArray: return p.parents[i] } return nil } func (p *PredictionContext) getReturnState(i int) int { switch p.pcType { case PredictionContextArray: return p.returnStates[i] default: return p.returnState } } func (p *PredictionContext) GetReturnStates() []int { switch p.pcType { case PredictionContextArray: return p.returnStates default: return []int{p.returnState} } } func (p *PredictionContext) length() int { switch p.pcType { case PredictionContextArray: return len(p.returnStates) default: return 1 } } func (p *PredictionContext) hasEmptyPath() bool { switch p.pcType { case PredictionContextSingleton: return p.returnState == BasePredictionContextEmptyReturnState } return p.getReturnState(p.length()-1) == BasePredictionContextEmptyReturnState } func (p *PredictionContext) String() string { switch p.pcType { case PredictionContextEmpty: return "$" case PredictionContextSingleton: var up string if p.parentCtx == nil { up = "" } else { up = p.parentCtx.String() } if len(up) == 0 { if p.returnState == BasePredictionContextEmptyReturnState { return "$" } return strconv.Itoa(p.returnState) } return strconv.Itoa(p.returnState) + " " + up case PredictionContextArray: if p.isEmpty() { return "[]" } s := "[" for i := 0; i < len(p.returnStates); i++ { if i > 0 { s = s + ", " } if p.returnStates[i] == BasePredictionContextEmptyReturnState { s = s + "$" continue } s = s + strconv.Itoa(p.returnStates[i]) if !p.parents[i].isEmpty() { s = s + " " + p.parents[i].String() } else { s = s + "nil" } } return s + "]" default: return "unknown" } } func (p *PredictionContext) isEmpty() bool { switch p.pcType { case PredictionContextEmpty: return true case PredictionContextArray: // since EmptyReturnState can only appear in the last position, we // don't need to verify that size==1 return p.returnStates[0] == BasePredictionContextEmptyReturnState default: return false } } func (p *PredictionContext) Type() int { return p.pcType } func calculateHash(parent *PredictionContext, returnState int) int { h := murmurInit(1) h = murmurUpdate(h, parent.Hash()) h = murmurUpdate(h, returnState) return murmurFinish(h, 2) } // Convert a {@link RuleContext} tree to a {@link BasePredictionContext} graph. // Return {@link //EMPTY} if {@code outerContext} is empty or nil. // / func predictionContextFromRuleContext(a *ATN, outerContext RuleContext) *PredictionContext { if outerContext == nil { outerContext = ParserRuleContextEmpty } // if we are in RuleContext of start rule, s, then BasePredictionContext // is EMPTY. Nobody called us. (if we are empty, return empty) if outerContext.GetParent() == nil || outerContext == ParserRuleContextEmpty { return BasePredictionContextEMPTY } // If we have a parent, convert it to a BasePredictionContext graph parent := predictionContextFromRuleContext(a, outerContext.GetParent().(RuleContext)) state := a.states[outerContext.GetInvokingState()] transition := state.GetTransitions()[0] return SingletonBasePredictionContextCreate(parent, transition.(*RuleTransition).followState.GetStateNumber()) } func merge(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMap) *PredictionContext { // Share same graph if both same // if a == b || a.Equals(b) { return a } if a.pcType == PredictionContextSingleton && b.pcType == PredictionContextSingleton { return mergeSingletons(a, b, rootIsWildcard, mergeCache) } // At least one of a or b is array // If one is $ and rootIsWildcard, return $ as wildcard if rootIsWildcard { if a.isEmpty() { return a } if b.isEmpty() { return b } } // Convert either Singleton or Empty to arrays, so that we can merge them // ara := convertToArray(a) arb := convertToArray(b) return mergeArrays(ara, arb, rootIsWildcard, mergeCache) } func convertToArray(pc *PredictionContext) *PredictionContext { switch pc.Type() { case PredictionContextEmpty: return NewArrayPredictionContext([]*PredictionContext{}, []int{}) case PredictionContextSingleton: return NewArrayPredictionContext([]*PredictionContext{pc.GetParent(0)}, []int{pc.getReturnState(0)}) default: // Already an array } return pc } // mergeSingletons merges two Singleton [PredictionContext] instances. // // Stack tops equal, parents merge is same return left graph. //

// //

Same stack top, parents differ merge parents giving array node, then // remainders of those graphs. A new root node is created to point to the // merged parents.
//

// //

Different stack tops pointing to same parent. Make array node for the // root where both element in the root point to the same (original) // parent.
//

// //

Different stack tops pointing to different parents. Make array node for // the root where each element points to the corresponding original // parent.
//

// // @param a the first {@link SingletonBasePredictionContext} // @param b the second {@link SingletonBasePredictionContext} // @param rootIsWildcard {@code true} if this is a local-context merge, // otherwise false to indicate a full-context merge // @param mergeCache // / func mergeSingletons(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMap) *PredictionContext { if mergeCache != nil { previous, present := mergeCache.Get(a, b) if present { return previous } previous, present = mergeCache.Get(b, a) if present { return previous } } rootMerge := mergeRoot(a, b, rootIsWildcard) if rootMerge != nil { if mergeCache != nil { mergeCache.Put(a, b, rootMerge) } return rootMerge } if a.returnState == b.returnState { parent := merge(a.parentCtx, b.parentCtx, rootIsWildcard, mergeCache) // if parent is same as existing a or b parent or reduced to a parent, // return it if parent.Equals(a.parentCtx) { return a // ax + bx = ax, if a=b } if parent.Equals(b.parentCtx) { return b // ax + bx = bx, if a=b } // else: ax + ay = a'[x,y] // merge parents x and y, giving array node with x,y then remainders // of those graphs. dup a, a' points at merged array. // New joined parent so create a new singleton pointing to it, a' spc := SingletonBasePredictionContextCreate(parent, a.returnState) if mergeCache != nil { mergeCache.Put(a, b, spc) } return spc } // a != b payloads differ // see if we can collapse parents due to $+x parents if local ctx var singleParent *PredictionContext if a.Equals(b) || (a.parentCtx != nil && a.parentCtx.Equals(b.parentCtx)) { // ax + // bx = // [a,b]x singleParent = a.parentCtx } if singleParent != nil { // parents are same // sort payloads and use same parent payloads := []int{a.returnState, b.returnState} if a.returnState > b.returnState { payloads[0] = b.returnState payloads[1] = a.returnState } parents := []*PredictionContext{singleParent, singleParent} apc := NewArrayPredictionContext(parents, payloads) if mergeCache != nil { mergeCache.Put(a, b, apc) } return apc } // parents differ and can't merge them. Just pack together // into array can't merge. // ax + by = [ax,by] payloads := []int{a.returnState, b.returnState} parents := []*PredictionContext{a.parentCtx, b.parentCtx} if a.returnState > b.returnState { // sort by payload payloads[0] = b.returnState payloads[1] = a.returnState parents = []*PredictionContext{b.parentCtx, a.parentCtx} } apc := NewArrayPredictionContext(parents, payloads) if mergeCache != nil { mergeCache.Put(a, b, apc) } return apc } // Handle case where at least one of {@code a} or {@code b} is // {@link //EMPTY}. In the following diagrams, the symbol {@code $} is used // to represent {@link //EMPTY}. // //

Local-Context Merges

// //

These local-context merge operations are used when {@code rootIsWildcard} // is true.

// //

{@link //EMPTY} is superset of any graph return {@link //EMPTY}.
//

// //

{@link //EMPTY} and anything is {@code //EMPTY}, so merged parent is // {@code //EMPTY} return left graph.
//

// //

Special case of last merge if local context.
//

// //

Full-Context Merges

// //

These full-context merge operations are used when {@code rootIsWildcard} // is false.

// //

// //

Must keep all contexts {@link //EMPTY} in array is a special value (and // nil parent).
//

// //

// // @param a the first {@link SingletonBasePredictionContext} // @param b the second {@link SingletonBasePredictionContext} // @param rootIsWildcard {@code true} if this is a local-context merge, // otherwise false to indicate a full-context merge // / func mergeRoot(a, b *PredictionContext, rootIsWildcard bool) *PredictionContext { if rootIsWildcard { if a.pcType == PredictionContextEmpty { return BasePredictionContextEMPTY // // + b =// } if b.pcType == PredictionContextEmpty { return BasePredictionContextEMPTY // a +// =// } } else { if a.isEmpty() && b.isEmpty() { return BasePredictionContextEMPTY // $ + $ = $ } else if a.isEmpty() { // $ + x = [$,x] payloads := []int{b.getReturnState(-1), BasePredictionContextEmptyReturnState} parents := []*PredictionContext{b.GetParent(-1), nil} return NewArrayPredictionContext(parents, payloads) } else if b.isEmpty() { // x + $ = [$,x] ($ is always first if present) payloads := []int{a.getReturnState(-1), BasePredictionContextEmptyReturnState} parents := []*PredictionContext{a.GetParent(-1), nil} return NewArrayPredictionContext(parents, payloads) } } return nil } // Merge two {@link ArrayBasePredictionContext} instances. // //

Different tops, different parents.
//

// //

Shared top, same parents.
//

// //

Shared top, different parents.
//

// //

Shared top, all shared parents.
//

// //

Equal tops, merge parents and reduce top to // {@link SingletonBasePredictionContext}.
//

// //goland:noinspection GoBoolExpressions func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMap) *PredictionContext { if mergeCache != nil { previous, present := mergeCache.Get(a, b) if present { if runtimeConfig.parserATNSimulatorTraceATNSim { fmt.Println("mergeArrays a=" + a.String() + ",b=" + b.String() + " -> previous") } return previous } previous, present = mergeCache.Get(b, a) if present { if runtimeConfig.parserATNSimulatorTraceATNSim { fmt.Println("mergeArrays a=" + a.String() + ",b=" + b.String() + " -> previous") } return previous } } // merge sorted payloads a + b => M i := 0 // walks a j := 0 // walks b k := 0 // walks target M array mergedReturnStates := make([]int, len(a.returnStates)+len(b.returnStates)) mergedParents := make([]*PredictionContext, len(a.returnStates)+len(b.returnStates)) // walk and merge to yield mergedParents, mergedReturnStates for i < len(a.returnStates) && j < len(b.returnStates) { aParent := a.parents[i] bParent := b.parents[j] if a.returnStates[i] == b.returnStates[j] { // same payload (stack tops are equal), must yield merged singleton payload := a.returnStates[i] // $+$ = $ bothDollars := payload == BasePredictionContextEmptyReturnState && aParent == nil && bParent == nil axAX := aParent != nil && bParent != nil && aParent.Equals(bParent) // ax+ax // -> // ax if bothDollars || axAX { mergedParents[k] = aParent // choose left mergedReturnStates[k] = payload } else { // ax+ay -> a'[x,y] mergedParent := merge(aParent, bParent, rootIsWildcard, mergeCache) mergedParents[k] = mergedParent mergedReturnStates[k] = payload } i++ // hop over left one as usual j++ // but also Skip one in right side since we merge } else if a.returnStates[i] < b.returnStates[j] { // copy a[i] to M mergedParents[k] = aParent mergedReturnStates[k] = a.returnStates[i] i++ } else { // b > a, copy b[j] to M mergedParents[k] = bParent mergedReturnStates[k] = b.returnStates[j] j++ } k++ } // copy over any payloads remaining in either array if i < len(a.returnStates) { for p := i; p < len(a.returnStates); p++ { mergedParents[k] = a.parents[p] mergedReturnStates[k] = a.returnStates[p] k++ } } else { for p := j; p < len(b.returnStates); p++ { mergedParents[k] = b.parents[p] mergedReturnStates[k] = b.returnStates[p] k++ } } // trim merged if we combined a few that had same stack tops if k < len(mergedParents) { // write index < last position trim if k == 1 { // for just one merged element, return singleton top pc := SingletonBasePredictionContextCreate(mergedParents[0], mergedReturnStates[0]) if mergeCache != nil { mergeCache.Put(a, b, pc) } return pc } mergedParents = mergedParents[0:k] mergedReturnStates = mergedReturnStates[0:k] } M := NewArrayPredictionContext(mergedParents, mergedReturnStates) // if we created same array as a or b, return that instead // TODO: JI track whether this is possible above during merge sort for speed and possibly avoid an allocation if M.Equals(a) { if mergeCache != nil { mergeCache.Put(a, b, a) } if runtimeConfig.parserATNSimulatorTraceATNSim { fmt.Println("mergeArrays a=" + a.String() + ",b=" + b.String() + " -> a") } return a } if M.Equals(b) { if mergeCache != nil { mergeCache.Put(a, b, b) } if runtimeConfig.parserATNSimulatorTraceATNSim { fmt.Println("mergeArrays a=" + a.String() + ",b=" + b.String() + " -> b") } return b } combineCommonParents(&mergedParents) if mergeCache != nil { mergeCache.Put(a, b, M) } if runtimeConfig.parserATNSimulatorTraceATNSim { fmt.Println("mergeArrays a=" + a.String() + ",b=" + b.String() + " -> " + M.String()) } return M } // Make pass over all M parents and merge any Equals() ones. // Note that we pass a pointer to the slice as we want to modify it in place. // //goland:noinspection GoUnusedFunction func combineCommonParents(parents *[]*PredictionContext) { uniqueParents := NewJStore[*PredictionContext, Comparator[*PredictionContext]](pContextEqInst, PredictionContextCollection, "combineCommonParents for PredictionContext") for p := 0; p < len(*parents); p++ { parent := (*parents)[p] _, _ = uniqueParents.Put(parent) } for q := 0; q < len(*parents); q++ { pc, _ := uniqueParents.Get((*parents)[q]) (*parents)[q] = pc } } func getCachedBasePredictionContext(context *PredictionContext, contextCache *PredictionContextCache, visited *VisitRecord) *PredictionContext { if context.isEmpty() { return context } existing, present := visited.Get(context) if present { return existing } existing, present = contextCache.Get(context) if present { visited.Put(context, existing) return existing } changed := false parents := make([]*PredictionContext, context.length()) for i := 0; i < len(parents); i++ { parent := getCachedBasePredictionContext(context.GetParent(i), contextCache, visited) if changed || !parent.Equals(context.GetParent(i)) { if !changed { parents = make([]*PredictionContext, context.length()) for j := 0; j < context.length(); j++ { parents[j] = context.GetParent(j) } changed = true } parents[i] = parent } } if !changed { contextCache.add(context) visited.Put(context, context) return context } var updated *PredictionContext if len(parents) == 0 { updated = BasePredictionContextEMPTY } else if len(parents) == 1 { updated = SingletonBasePredictionContextCreate(parents[0], context.getReturnState(0)) } else { updated = NewArrayPredictionContext(parents, context.GetReturnStates()) } contextCache.add(updated) visited.Put(updated, updated) visited.Put(context, updated) return updated }