braces.go

  1// Copyright (c) 2018, Daniel MartΓ­ <mvdan@mvdan.cc>
  2// See LICENSE for licensing information
  3
  4package syntax
  5
  6import "strconv"
  7
  8var (
  9	litLeftBrace  = &Lit{Value: "{"}
 10	litComma      = &Lit{Value: ","}
 11	litDots       = &Lit{Value: ".."}
 12	litRightBrace = &Lit{Value: "}"}
 13)
 14
 15// SplitBraces parses brace expansions within a word's literal parts. If any
 16// valid brace expansions are found, they are replaced with BraceExp nodes, and
 17// the function returns true. Otherwise, the word is left untouched and the
 18// function returns false.
 19//
 20// For example, a literal word "foo{bar,baz}" will result in a word containing
 21// the literal "foo", and a brace expansion with the elements "bar" and "baz".
 22//
 23// It does not return an error; malformed brace expansions are simply skipped.
 24// For example, the literal word "a{b" is left unchanged.
 25func SplitBraces(word *Word) bool {
 26	toSplit := false
 27	top := &Word{}
 28	acc := top
 29	var cur *BraceExp
 30	open := []*BraceExp{}
 31
 32	pop := func() *BraceExp {
 33		old := cur
 34		open = open[:len(open)-1]
 35		if len(open) == 0 {
 36			cur = nil
 37			acc = top
 38		} else {
 39			cur = open[len(open)-1]
 40			acc = cur.Elems[len(cur.Elems)-1]
 41		}
 42		return old
 43	}
 44	addLit := func(lit *Lit) {
 45		acc.Parts = append(acc.Parts, lit)
 46	}
 47
 48	for _, wp := range word.Parts {
 49		lit, ok := wp.(*Lit)
 50		if !ok {
 51			acc.Parts = append(acc.Parts, wp)
 52			continue
 53		}
 54		last := 0
 55		for j := 0; j < len(lit.Value); j++ {
 56			addlitidx := func() {
 57				if last == j {
 58					return // empty lit
 59				}
 60				l2 := *lit
 61				l2.Value = l2.Value[last:j]
 62				addLit(&l2)
 63			}
 64			switch lit.Value[j] {
 65			case '{':
 66				addlitidx()
 67				acc = &Word{}
 68				cur = &BraceExp{Elems: []*Word{acc}}
 69				open = append(open, cur)
 70			case ',':
 71				if cur == nil {
 72					continue
 73				}
 74				addlitidx()
 75				acc = &Word{}
 76				cur.Elems = append(cur.Elems, acc)
 77			case '.':
 78				if cur == nil {
 79					continue
 80				}
 81				if j+1 >= len(lit.Value) || lit.Value[j+1] != '.' {
 82					continue
 83				}
 84				addlitidx()
 85				cur.Sequence = true
 86				acc = &Word{}
 87				cur.Elems = append(cur.Elems, acc)
 88				j++
 89			case '}':
 90				if cur == nil {
 91					continue
 92				}
 93				toSplit = true
 94				addlitidx()
 95				br := pop()
 96				if len(br.Elems) == 1 {
 97					// return {x} to a non-brace
 98					addLit(litLeftBrace)
 99					acc.Parts = append(acc.Parts, br.Elems[0].Parts...)
100					addLit(litRightBrace)
101					break
102				}
103				if !br.Sequence {
104					acc.Parts = append(acc.Parts, br)
105					break
106				}
107				var chars [2]bool
108				broken := false
109				for i, elem := range br.Elems[:2] {
110					val := elem.Lit()
111					if _, err := strconv.Atoi(val); err == nil {
112					} else if len(val) == 1 &&
113						(('a' <= val[0] && val[0] <= 'z') ||
114							('A' <= val[0] && val[0] <= 'Z')) {
115						chars[i] = true
116					} else {
117						broken = true
118					}
119				}
120				if len(br.Elems) == 3 {
121					// increment must be a number
122					val := br.Elems[2].Lit()
123					if _, err := strconv.Atoi(val); err != nil {
124						broken = true
125					}
126				}
127				// are start and end both chars or
128				// non-chars?
129				if chars[0] != chars[1] {
130					broken = true
131				}
132				if !broken {
133					acc.Parts = append(acc.Parts, br)
134					break
135				}
136				// return broken {x..y[..incr]} to a non-brace
137				addLit(litLeftBrace)
138				for i, elem := range br.Elems {
139					if i > 0 {
140						addLit(litDots)
141					}
142					acc.Parts = append(acc.Parts, elem.Parts...)
143				}
144				addLit(litRightBrace)
145			default:
146				continue
147			}
148			last = j + 1
149		}
150		if last == 0 {
151			addLit(lit)
152		} else {
153			left := *lit
154			left.Value = left.Value[last:]
155			addLit(&left)
156		}
157	}
158	if !toSplit {
159		return false
160	}
161	// open braces that were never closed fall back to non-braces
162	for acc != top {
163		br := pop()
164		addLit(litLeftBrace)
165		for i, elem := range br.Elems {
166			if i > 0 {
167				if br.Sequence {
168					addLit(litDots)
169				} else {
170					addLit(litComma)
171				}
172			}
173			acc.Parts = append(acc.Parts, elem.Parts...)
174		}
175	}
176	*word = *top
177	return true
178}