lexmachine/dfa/dfa_helpers.go
2022-08-24 15:59:27 +08:00

280 lines
8.0 KiB
Go

package dfa
import (
"fmt"
"gitea.xintech.co/zhouzhihong/lexmachine/frontend"
)
// LabeledAST is a post-order labeled version of the AST. The root the node will be Order[len(Order)-1].
type LabeledAST struct {
Root frontend.AST // root of the AST
Order []frontend.AST // post-order labeling of the all the nodes
Kids [][]int // a lookup table of location for each of the nodes children
Positions []int // a labeling of all the position nodes (Character/Range) in the AST.
posmap map[int]int // maps an order index to a pos index.
Matches []int // the index (in Positions) of all the EOS nodes
nullable []bool
first [][]int
last [][]int
follow []map[int]bool
}
// Label a tree and its "positions" (Character/Range nodes) in post-order
// notation.
func Label(ast frontend.AST) *LabeledAST {
type entry struct {
n frontend.AST
i int
kids []int
}
order := make([]frontend.AST, 0, 10)
children := make([][]int, 0, 10)
positions := make([]int, 0, 10)
matches := make([]int, 0, 10)
posmap := make(map[int]int)
stack := make([]entry, 0, 10)
stack = append(stack, entry{ast, 0, []int{}})
for len(stack) > 0 {
var c entry
stack, c = stack[:len(stack)-1], stack[len(stack)-1]
kids := c.n.Children()
for c.i < len(kids) {
kid := kids[c.i]
stack = append(stack, entry{c.n, c.i + 1, c.kids})
c = entry{kid, 0, []int{}}
kids = c.n.Children()
}
oid := len(order)
if len(stack) > 0 {
stack[len(stack)-1].kids = append(stack[len(stack)-1].kids, oid)
}
order = append(order, c.n)
children = append(children, c.kids)
switch c.n.(type) {
case *frontend.Character, *frontend.Range:
posmap[oid] = len(positions)
positions = append(positions, oid)
case *frontend.EOS:
pid := len(positions)
posmap[oid] = pid
positions = append(positions, oid)
matches = append(matches, pid)
}
}
return &LabeledAST{
Root: ast,
Order: order,
Kids: children,
Positions: positions,
posmap: posmap,
Matches: matches,
}
}
func (a *LabeledAST) pos(oid int) int {
if pid, has := a.posmap[oid]; !has {
panic("Passed a bad order id into Position (likely used a non-position node's id)")
} else {
return pid
}
}
// Follow computes look up tables for each Position (leaf node) in the tree
// which indicates what other Position could follow the current position in a
// matching string. It also computes what positions appear first in the tree.
func (a *LabeledAST) Follow() (firstOfTree []int, follow []map[int]bool) {
positions := a.Positions
nullable := a.MatchesEmptyString()
first := a.First()
last := a.Last()
// get the first of the whole ast by retrieving the first for the root (len(order)-1).
firstOfTree = make([]int, 0, len(first[len(a.Order)-1]))
for _, p := range first[len(a.Order)-1] {
firstOfTree = append(firstOfTree, p)
}
if a.follow != nil {
return firstOfTree, a.follow
}
follow = make([]map[int]bool, len(positions))
for i := range follow {
follow[i] = make(map[int]bool)
}
for i, node := range a.Order {
switch n := node.(type) {
case *frontend.Concat:
for x := 0; x < len(n.Items)-1; x++ {
j := a.Kids[i][x]
kFirst := make([]int, 0, 10)
for y := x + 1; y < len(n.Items); y++ {
k := a.Kids[i][y]
for _, p := range first[k] {
kFirst = append(kFirst, p)
}
if !nullable[k] {
break
}
}
for _, p := range last[j] {
for _, q := range kFirst {
follow[p][q] = true
}
}
}
case *frontend.Star, *frontend.Plus:
nFirst := make([]int, 0, 10)
for _, p := range first[i] {
nFirst = append(nFirst, p)
}
for _, p := range last[i] {
for _, q := range nFirst {
follow[p][q] = true
}
}
}
}
a.follow = follow
return firstOfTree, follow
}
// MatchesEmptyString computes a look up table for each node in the tree (in
// post order, matching the Order member) on whether or not the subtree rooted
// in each node matches the empty string.
func (a *LabeledAST) MatchesEmptyString() (nullable []bool) {
if a.nullable != nil {
return a.nullable
}
nullable = make([]bool, 0, len(a.Order))
for i, node := range a.Order {
switch n := node.(type) {
case *frontend.EOS:
nullable = append(nullable, true)
case *frontend.Character:
nullable = append(nullable, false)
case *frontend.Range:
nullable = append(nullable, false)
case *frontend.Maybe:
nullable = append(nullable, true)
case *frontend.Plus:
nullable = append(nullable, nullable[a.Kids[i][0]])
case *frontend.Star:
nullable = append(nullable, true)
case *frontend.Match:
nullable = append(nullable, nullable[a.Kids[i][0]])
case *frontend.Alternation:
nullable = append(nullable, nullable[a.Kids[i][0]] || nullable[a.Kids[i][1]])
case *frontend.AltMatch:
nullable = append(nullable, nullable[a.Kids[i][0]] || nullable[a.Kids[i][1]])
case *frontend.Concat:
epsilon := true
for _, j := range a.Kids[i] {
epsilon = epsilon && nullable[j]
}
nullable = append(nullable, epsilon)
default:
panic(fmt.Errorf("Unexpected type %T", n))
}
}
a.nullable = nullable
return nullable
}
// First computes a look up table for each node in the tree (in post order,
// matching the Order member) indicating for the subtree rooted at each node
// which positions (leaf nodes indexes in the Positions slice) will appear at
// the beginning of a string matching that subtree.
func (a *LabeledAST) First() (first [][]int) {
if a.first != nil {
return a.first
}
nullable := a.MatchesEmptyString()
first = make([][]int, 0, len(a.Order))
for i, node := range a.Order {
switch n := node.(type) {
case *frontend.EOS:
first = append(first, []int{a.pos(i)})
case *frontend.Character:
first = append(first, []int{a.pos(i)})
case *frontend.Range:
first = append(first, []int{a.pos(i)})
case *frontend.Maybe:
first = append(first, first[a.Kids[i][0]])
case *frontend.Plus:
first = append(first, first[a.Kids[i][0]])
case *frontend.Star:
first = append(first, first[a.Kids[i][0]])
case *frontend.Match:
first = append(first, first[a.Kids[i][0]])
case *frontend.Alternation:
first = append(first, append(first[a.Kids[i][0]], first[a.Kids[i][1]]...))
case *frontend.AltMatch:
first = append(first, append(first[a.Kids[i][0]], first[a.Kids[i][1]]...))
case *frontend.Concat:
f := make([]int, 0, len(n.Items))
for _, j := range a.Kids[i] {
f = append(f, first[j]...)
if !nullable[j] {
break
}
}
first = append(first, f)
default:
panic(fmt.Errorf("Unexpected type %T", n))
}
}
a.first = first
return first
}
// Last computes a look up table for each node in the tree (in post order,
// matching the Order member) indicating for the subtree rooted at each node
// which positions (leaf nodes indexes in the Positions slice) will appear at
// the end of a string matching that subtree.
func (a *LabeledAST) Last() (last [][]int) {
if a.last != nil {
return a.last
}
nullable := a.MatchesEmptyString()
last = make([][]int, 0, len(a.Order))
for i, node := range a.Order {
switch n := node.(type) {
case *frontend.EOS:
last = append(last, []int{a.pos(i)})
case *frontend.Character:
last = append(last, []int{a.pos(i)})
case *frontend.Range:
last = append(last, []int{a.pos(i)})
case *frontend.Maybe:
last = append(last, last[a.Kids[i][0]])
case *frontend.Plus:
last = append(last, last[a.Kids[i][0]])
case *frontend.Star:
last = append(last, last[a.Kids[i][0]])
case *frontend.Match:
last = append(last, last[a.Kids[i][0]])
case *frontend.Alternation:
last = append(last, append(last[a.Kids[i][0]], last[a.Kids[i][1]]...))
case *frontend.AltMatch:
last = append(last, append(last[a.Kids[i][0]], last[a.Kids[i][1]]...))
case *frontend.Concat:
l := make([]int, 0, len(n.Items))
for x := len(n.Items) - 1; x >= 0; x-- {
j := a.Kids[i][x]
l = append(l, last[j]...)
if !nullable[j] {
break
}
}
last = append(last, l)
default:
panic(fmt.Errorf("Unexpected type %T", n))
}
}
a.last = last
return last
}