github.com/cilki/sh@v2.6.4+incompatible/syntax/expand.go (about)

     1  // Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
     2  // See LICENSE for licensing information
     3  
     4  package syntax
     5  
     6  import "strconv"
     7  
     8  // TODO(v3): Consider making these special syntax nodes.
     9  // Among other things, we can make use of Word.Lit.
    10  
    11  type brace struct {
    12  	seq   bool // {x..y[..incr]} instead of {x,y[,...]}
    13  	chars bool // sequence is of chars, not numbers
    14  	elems []*braceWord
    15  }
    16  
    17  // braceWord is like Word, but with braceWordPart.
    18  type braceWord struct {
    19  	parts []braceWordPart
    20  }
    21  
    22  // braceWordPart contains any WordPart or a brace.
    23  type braceWordPart interface{}
    24  
    25  var (
    26  	litLeftBrace  = &Lit{Value: "{"}
    27  	litComma      = &Lit{Value: ","}
    28  	litDots       = &Lit{Value: ".."}
    29  	litRightBrace = &Lit{Value: "}"}
    30  )
    31  
    32  func splitBraces(word *Word) (*braceWord, bool) {
    33  	any := false
    34  	top := &braceWord{}
    35  	acc := top
    36  	var cur *brace
    37  	open := []*brace{}
    38  
    39  	pop := func() *brace {
    40  		old := cur
    41  		open = open[:len(open)-1]
    42  		if len(open) == 0 {
    43  			cur = nil
    44  			acc = top
    45  		} else {
    46  			cur = open[len(open)-1]
    47  			acc = cur.elems[len(cur.elems)-1]
    48  		}
    49  		return old
    50  	}
    51  	addLit := func(lit *Lit) {
    52  		acc.parts = append(acc.parts, lit)
    53  	}
    54  	addParts := func(parts ...braceWordPart) {
    55  		acc.parts = append(acc.parts, parts...)
    56  	}
    57  
    58  	for _, wp := range word.Parts {
    59  		lit, ok := wp.(*Lit)
    60  		if !ok {
    61  			addParts(wp)
    62  			continue
    63  		}
    64  		last := 0
    65  		for j := 0; j < len(lit.Value); j++ {
    66  			addlitidx := func() {
    67  				if last == j {
    68  					return // empty lit
    69  				}
    70  				l2 := *lit
    71  				l2.Value = l2.Value[last:j]
    72  				addLit(&l2)
    73  			}
    74  			switch lit.Value[j] {
    75  			case '{':
    76  				addlitidx()
    77  				acc = &braceWord{}
    78  				cur = &brace{elems: []*braceWord{acc}}
    79  				open = append(open, cur)
    80  			case ',':
    81  				if cur == nil {
    82  					continue
    83  				}
    84  				addlitidx()
    85  				acc = &braceWord{}
    86  				cur.elems = append(cur.elems, acc)
    87  			case '.':
    88  				if cur == nil {
    89  					continue
    90  				}
    91  				if j+1 >= len(lit.Value) || lit.Value[j+1] != '.' {
    92  					continue
    93  				}
    94  				addlitidx()
    95  				cur.seq = true
    96  				acc = &braceWord{}
    97  				cur.elems = append(cur.elems, acc)
    98  				j++
    99  			case '}':
   100  				if cur == nil {
   101  					continue
   102  				}
   103  				any = true
   104  				addlitidx()
   105  				br := pop()
   106  				if len(br.elems) == 1 {
   107  					// return {x} to a non-brace
   108  					addLit(litLeftBrace)
   109  					addParts(br.elems[0].parts...)
   110  					addLit(litRightBrace)
   111  					break
   112  				}
   113  				if !br.seq {
   114  					addParts(br)
   115  					break
   116  				}
   117  				var chars [2]bool
   118  				broken := false
   119  				for i, elem := range br.elems[:2] {
   120  					val := braceWordLit(elem)
   121  					if _, err := strconv.Atoi(val); err == nil {
   122  					} else if len(val) == 1 &&
   123  						'a' <= val[0] && val[0] <= 'z' {
   124  						chars[i] = true
   125  					} else {
   126  						broken = true
   127  					}
   128  				}
   129  				if len(br.elems) == 3 {
   130  					// increment must be a number
   131  					val := braceWordLit(br.elems[2])
   132  					if _, err := strconv.Atoi(val); err != nil {
   133  						broken = true
   134  					}
   135  				}
   136  				// are start and end both chars or
   137  				// non-chars?
   138  				if chars[0] != chars[1] {
   139  					broken = true
   140  				}
   141  				if !broken {
   142  					br.chars = chars[0]
   143  					addParts(br)
   144  					break
   145  				}
   146  				// return broken {x..y[..incr]} to a non-brace
   147  				addLit(litLeftBrace)
   148  				for i, elem := range br.elems {
   149  					if i > 0 {
   150  						addLit(litDots)
   151  					}
   152  					addParts(elem.parts...)
   153  				}
   154  				addLit(litRightBrace)
   155  			default:
   156  				continue
   157  			}
   158  			last = j + 1
   159  		}
   160  		if last == 0 {
   161  			addLit(lit)
   162  		} else {
   163  			left := *lit
   164  			left.Value = left.Value[last:]
   165  			addLit(&left)
   166  		}
   167  	}
   168  	// open braces that were never closed fall back to non-braces
   169  	for acc != top {
   170  		br := pop()
   171  		addLit(litLeftBrace)
   172  		for i, elem := range br.elems {
   173  			if i > 0 {
   174  				if br.seq {
   175  					addLit(litDots)
   176  				} else {
   177  					addLit(litComma)
   178  				}
   179  			}
   180  			addParts(elem.parts...)
   181  		}
   182  	}
   183  	return top, any
   184  }
   185  
   186  func braceWordLit(v interface{}) string {
   187  	word, _ := v.(*braceWord)
   188  	if word == nil || len(word.parts) != 1 {
   189  		return ""
   190  	}
   191  	lit, ok := word.parts[0].(*Lit)
   192  	if !ok {
   193  		return ""
   194  	}
   195  	return lit.Value
   196  }
   197  
   198  func expandRec(bw *braceWord) []*Word {
   199  	var all []*Word
   200  	var left []WordPart
   201  	for i, wp := range bw.parts {
   202  		br, ok := wp.(*brace)
   203  		if !ok {
   204  			left = append(left, wp.(WordPart))
   205  			continue
   206  		}
   207  		if br.seq {
   208  			var from, to int
   209  			if br.chars {
   210  				from = int(braceWordLit(br.elems[0])[0])
   211  				to = int(braceWordLit(br.elems[1])[0])
   212  			} else {
   213  				from, _ = strconv.Atoi(braceWordLit(br.elems[0]))
   214  				to, _ = strconv.Atoi(braceWordLit(br.elems[1]))
   215  			}
   216  			upward := from <= to
   217  			incr := 1
   218  			if !upward {
   219  				incr = -1
   220  			}
   221  			if len(br.elems) > 2 {
   222  				val := braceWordLit(br.elems[2])
   223  				n, _ := strconv.Atoi(val)
   224  				if n != 0 && n > 0 == upward {
   225  					incr = n
   226  				}
   227  			}
   228  			n := from
   229  			for {
   230  				if upward && n > to {
   231  					break
   232  				}
   233  				if !upward && n < to {
   234  					break
   235  				}
   236  				next := *bw
   237  				next.parts = next.parts[i+1:]
   238  				lit := &Lit{}
   239  				if br.chars {
   240  					lit.Value = string(n)
   241  				} else {
   242  					lit.Value = strconv.Itoa(n)
   243  				}
   244  				next.parts = append([]braceWordPart{lit}, next.parts...)
   245  				exp := expandRec(&next)
   246  				for _, w := range exp {
   247  					w.Parts = append(left, w.Parts...)
   248  				}
   249  				all = append(all, exp...)
   250  				n += incr
   251  			}
   252  			return all
   253  		}
   254  		for _, elem := range br.elems {
   255  			next := *bw
   256  			next.parts = next.parts[i+1:]
   257  			next.parts = append(elem.parts, next.parts...)
   258  			exp := expandRec(&next)
   259  			for _, w := range exp {
   260  				w.Parts = append(left, w.Parts...)
   261  			}
   262  			all = append(all, exp...)
   263  		}
   264  		return all
   265  	}
   266  	return []*Word{{Parts: left}}
   267  }
   268  
   269  // TODO(v3): remove
   270  
   271  // ExpandBraces performs Bash brace expansion on a word. For example,
   272  // passing it a single-literal word "foo{bar,baz}" will return two
   273  // single-literal words, "foobar" and "foobaz".
   274  //
   275  // Deprecated: use mvdan.cc/sh/expand.Braces instead.
   276  func ExpandBraces(word *Word) []*Word {
   277  	topBrace, any := splitBraces(word)
   278  	if !any {
   279  		return []*Word{word}
   280  	}
   281  	return expandRec(topBrace)
   282  }