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}