1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
// Package np provides utilities for working with node paths from a leaf of a
// parse tree to the root.
package np
import (
"src.elv.sh/pkg/eval"
"src.elv.sh/pkg/parse"
)
// Path is a path from a leaf in a parse tree to the root.
type Path []parse.Node
// Find finds the path of nodes from the leaf at position p to the root.
func Find(root parse.Node, p int) Path { return find(root, p, false) }
// FindLeft finds the path of nodes from the leaf at position p to the root. If
// p points to the start of one node (p == x.From), FindLeft finds the node to
// the left instead (y s.t. p == y.To).
func FindLeft(root parse.Node, p int) Path { return find(root, p, true) }
func find(root parse.Node, p int, preferLeft bool) Path {
n := root
descend:
for len(parse.Children(n)) > 0 {
for _, ch := range parse.Children(n) {
r := ch.Range()
if r.From <= p && p < r.To || preferLeft && p == r.To {
n = ch
continue descend
}
}
return nil
}
var path []parse.Node
for {
path = append(path, n)
if n == root {
break
}
n = parse.Parent(n)
}
return path
}
// Match matches against matchers, and returns whether all matches have
// succeeded.
func (p Path) Match(ms ...Matcher) bool {
for _, m := range ms {
p2, ok := m.Match(p)
if !ok {
return false
}
p = p2
}
return true
}
// Matcher wraps the Match method.
type Matcher interface {
// Match takes a slice of nodes and returns the remaining nodes and whether
// the match succeeded.
Match([]parse.Node) ([]parse.Node, bool)
}
// Typed returns a [Matcher] matching one node of a given type.
func Typed[T parse.Node]() Matcher { return typedMatcher[T]{} }
// Commonly used [Typed] matchers.
var (
Chunk = Typed[*parse.Chunk]()
Pipeline = Typed[*parse.Pipeline]()
Array = Typed[*parse.Array]()
Redir = Typed[*parse.Redir]()
Sep = Typed[*parse.Sep]()
)
type typedMatcher[T parse.Node] struct{}
func (m typedMatcher[T]) Match(ns []parse.Node) ([]parse.Node, bool) {
if len(ns) > 0 {
if _, ok := ns[0].(T); ok {
return ns[1:], true
}
}
return nil, false
}
// Store returns a [Matcher] matching one node of a given type, and stores it
// if a match succeeds.
func Store[T parse.Node](p *T) Matcher { return storeMatcher[T]{p} }
type storeMatcher[T parse.Node] struct{ p *T }
func (m storeMatcher[T]) Match(ns []parse.Node) ([]parse.Node, bool) {
if len(ns) > 0 {
if n, ok := ns[0].(T); ok {
*m.p = n
return ns[1:], true
}
}
return nil, false
}
// SimpleExpr returns a [Matcher] matching a "simple expression", which consists
// of 3 nodes from the leaf upwards (Primary, Indexing and Compound) and where
// the Compound expression can be evaluated statically using ev.
func SimpleExpr(data *SimpleExprData, ev *eval.Evaler) Matcher {
return simpleExprMatcher{data, ev}
}
// SimpleExprData contains useful data written by the [SimpleExpr] matcher.
type SimpleExprData struct {
Value string
Compound *parse.Compound
PrimarType parse.PrimaryType
}
type simpleExprMatcher struct {
data *SimpleExprData
ev *eval.Evaler
}
func (m simpleExprMatcher) Match(ns []parse.Node) ([]parse.Node, bool) {
if len(ns) < 3 {
return nil, false
}
primary, ok := ns[0].(*parse.Primary)
if !ok {
return nil, false
}
indexing, ok := ns[1].(*parse.Indexing)
if !ok {
return nil, false
}
compound, ok := ns[2].(*parse.Compound)
if !ok {
return nil, false
}
value, ok := m.ev.PurelyEvalPartialCompound(compound, indexing.To)
if !ok {
return nil, false
}
*m.data = SimpleExprData{value, compound, primary.Type}
return ns[3:], true
}
|