src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/parse/np/np.go (about)

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