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

     1  // Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
     2  // See LICENSE for licensing information
     3  
     4  package syntax
     5  
     6  import "bytes"
     7  
     8  // Simplify simplifies a given program and returns whether any changes
     9  // were made.
    10  //
    11  // The changes currently applied are:
    12  //
    13  //     Remove clearly useless parentheses       $(( (expr) ))
    14  //     Remove dollars from vars in exprs        (($var))
    15  //     Remove duplicate subshells               $( (stmts) )
    16  //     Remove redundant quotes                  [[ "$var" == str ]]
    17  //     Merge negations with unary operators     [[ ! -n $var ]]
    18  //     Use single quotes to shorten literals    "\$foo"
    19  func Simplify(n Node) bool {
    20  	s := simplifier{}
    21  	Walk(n, s.visit)
    22  	return s.modified
    23  }
    24  
    25  type simplifier struct {
    26  	modified bool
    27  }
    28  
    29  func (s *simplifier) visit(node Node) bool {
    30  	switch x := node.(type) {
    31  	case *Assign:
    32  		x.Index = s.removeParensArithm(x.Index)
    33  		// Don't inline params, as x[i] and x[$i] mean
    34  		// different things when x is an associative
    35  		// array; the first means "i", the second "$i".
    36  	case *ParamExp:
    37  		x.Index = s.removeParensArithm(x.Index)
    38  		// don't inline params - same as above.
    39  
    40  		if x.Slice == nil {
    41  			break
    42  		}
    43  		x.Slice.Offset = s.removeParensArithm(x.Slice.Offset)
    44  		x.Slice.Offset = s.inlineSimpleParams(x.Slice.Offset)
    45  		x.Slice.Length = s.removeParensArithm(x.Slice.Length)
    46  		x.Slice.Length = s.inlineSimpleParams(x.Slice.Length)
    47  	case *ArithmExp:
    48  		x.X = s.removeParensArithm(x.X)
    49  		x.X = s.inlineSimpleParams(x.X)
    50  	case *ArithmCmd:
    51  		x.X = s.removeParensArithm(x.X)
    52  		x.X = s.inlineSimpleParams(x.X)
    53  	case *ParenArithm:
    54  		x.X = s.removeParensArithm(x.X)
    55  		x.X = s.inlineSimpleParams(x.X)
    56  	case *BinaryArithm:
    57  		x.X = s.inlineSimpleParams(x.X)
    58  		x.Y = s.inlineSimpleParams(x.Y)
    59  	case *CmdSubst:
    60  		x.Stmts = s.inlineSubshell(x.Stmts)
    61  	case *Subshell:
    62  		x.Stmts = s.inlineSubshell(x.Stmts)
    63  	case *Word:
    64  		x.Parts = s.simplifyWord(x.Parts)
    65  	case *TestClause:
    66  		x.X = s.removeParensTest(x.X)
    67  		x.X = s.removeNegateTest(x.X)
    68  	case *ParenTest:
    69  		x.X = s.removeParensTest(x.X)
    70  		x.X = s.removeNegateTest(x.X)
    71  	case *BinaryTest:
    72  		x.X = s.unquoteParams(x.X)
    73  		x.X = s.removeNegateTest(x.X)
    74  		switch x.Op {
    75  		case TsMatch, TsNoMatch:
    76  			// unquoting enables globbing
    77  		default:
    78  			x.Y = s.unquoteParams(x.Y)
    79  		}
    80  		x.Y = s.removeNegateTest(x.Y)
    81  	case *UnaryTest:
    82  		x.X = s.unquoteParams(x.X)
    83  	}
    84  	return true
    85  }
    86  
    87  func (s *simplifier) simplifyWord(wps []WordPart) []WordPart {
    88  parts:
    89  	for i, wp := range wps {
    90  		dq, _ := wp.(*DblQuoted)
    91  		if dq == nil || len(dq.Parts) != 1 {
    92  			break
    93  		}
    94  		lit, _ := dq.Parts[0].(*Lit)
    95  		if lit == nil {
    96  			break
    97  		}
    98  		var buf bytes.Buffer
    99  		escaped := false
   100  		for _, r := range lit.Value {
   101  			switch r {
   102  			case '\\':
   103  				escaped = !escaped
   104  				if escaped {
   105  					continue
   106  				}
   107  			case '\'':
   108  				continue parts
   109  			case '$', '"', '`':
   110  				escaped = false
   111  			default:
   112  				if escaped {
   113  					continue parts
   114  				}
   115  				escaped = false
   116  			}
   117  			buf.WriteRune(r)
   118  		}
   119  		newVal := buf.String()
   120  		if newVal == lit.Value {
   121  			break
   122  		}
   123  		s.modified = true
   124  		wps[i] = &SglQuoted{
   125  			Left:   dq.Pos(),
   126  			Right:  dq.End(),
   127  			Dollar: dq.Dollar,
   128  			Value:  newVal,
   129  		}
   130  	}
   131  	return wps
   132  }
   133  
   134  func (s *simplifier) removeParensArithm(x ArithmExpr) ArithmExpr {
   135  	for {
   136  		par, _ := x.(*ParenArithm)
   137  		if par == nil {
   138  			return x
   139  		}
   140  		s.modified = true
   141  		x = par.X
   142  	}
   143  }
   144  
   145  func (s *simplifier) inlineSimpleParams(x ArithmExpr) ArithmExpr {
   146  	w, _ := x.(*Word)
   147  	if w == nil || len(w.Parts) != 1 {
   148  		return x
   149  	}
   150  	pe, _ := w.Parts[0].(*ParamExp)
   151  	if pe == nil || !ValidName(pe.Param.Value) {
   152  		return x
   153  	}
   154  	if pe.Excl || pe.Length || pe.Width || pe.Slice != nil ||
   155  		pe.Repl != nil || pe.Exp != nil {
   156  		return x
   157  	}
   158  	if pe.Index != nil {
   159  		s.modified = true
   160  		pe.Short = true
   161  		return w
   162  	}
   163  	s.modified = true
   164  	return &Word{Parts: []WordPart{pe.Param}}
   165  }
   166  
   167  func (s *simplifier) inlineSubshell(stmts []*Stmt) []*Stmt {
   168  	for len(stmts) == 1 {
   169  		st := stmts[0]
   170  		if st.Negated || st.Background || st.Coprocess ||
   171  			len(st.Redirs) > 0 {
   172  			break
   173  		}
   174  		sub, _ := st.Cmd.(*Subshell)
   175  		if sub == nil {
   176  			break
   177  		}
   178  		s.modified = true
   179  		stmts = sub.Stmts
   180  	}
   181  	return stmts
   182  }
   183  
   184  func (s *simplifier) unquoteParams(x TestExpr) TestExpr {
   185  	w, _ := x.(*Word)
   186  	if w == nil || len(w.Parts) != 1 {
   187  		return x
   188  	}
   189  	dq, _ := w.Parts[0].(*DblQuoted)
   190  	if dq == nil || len(dq.Parts) != 1 {
   191  		return x
   192  	}
   193  	if _, ok := dq.Parts[0].(*ParamExp); !ok {
   194  		return x
   195  	}
   196  	s.modified = true
   197  	w.Parts = dq.Parts
   198  	return w
   199  }
   200  
   201  func (s *simplifier) removeParensTest(x TestExpr) TestExpr {
   202  	for {
   203  		par, _ := x.(*ParenTest)
   204  		if par == nil {
   205  			return x
   206  		}
   207  		s.modified = true
   208  		x = par.X
   209  	}
   210  }
   211  
   212  func (s *simplifier) removeNegateTest(x TestExpr) TestExpr {
   213  	u, _ := x.(*UnaryTest)
   214  	if u == nil || u.Op != TsNot {
   215  		return x
   216  	}
   217  	switch y := u.X.(type) {
   218  	case *UnaryTest:
   219  		switch y.Op {
   220  		case TsEmpStr:
   221  			y.Op = TsNempStr
   222  			s.modified = true
   223  			return y
   224  		case TsNempStr:
   225  			y.Op = TsEmpStr
   226  			s.modified = true
   227  			return y
   228  		case TsNot:
   229  			s.modified = true
   230  			return y.X
   231  		}
   232  	case *BinaryTest:
   233  		switch y.Op {
   234  		case TsMatch:
   235  			y.Op = TsNoMatch
   236  			s.modified = true
   237  			return y
   238  		case TsNoMatch:
   239  			y.Op = TsMatch
   240  			s.modified = true
   241  			return y
   242  		}
   243  	}
   244  	return x
   245  }