File: np.go

package info (click to toggle)
elvish 0.21.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,372 kB
  • sloc: javascript: 236; sh: 130; python: 104; makefile: 88; xml: 9
file content (145 lines) | stat: -rw-r--r-- 3,598 bytes parent folder | download
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
}