1// Copyright 2015 Google Inc. All rights reserved 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package kati 16 17import ( 18 "bytes" 19 "errors" 20 "fmt" 21 "strings" 22) 23 24type pattern struct { 25 prefix, suffix string 26} 27 28func (p pattern) String() string { 29 return p.prefix + "%" + p.suffix 30} 31 32func (p pattern) match(s string) bool { 33 return strings.HasPrefix(s, p.prefix) && strings.HasSuffix(s, p.suffix) 34} 35 36func (p pattern) subst(repl, str string) string { 37 in := str 38 trimed := str 39 if p.prefix != "" { 40 trimed = strings.TrimPrefix(in, p.prefix) 41 if trimed == in { 42 return str 43 } 44 } 45 in = trimed 46 if p.suffix != "" { 47 trimed = strings.TrimSuffix(in, p.suffix) 48 if trimed == in { 49 return str 50 } 51 } 52 rs := strings.SplitN(repl, "%", 2) 53 if len(rs) != 2 { 54 return repl 55 } 56 return rs[0] + trimed + rs[1] 57} 58 59type rule struct { 60 srcpos 61 // outputs is output of the rule. 62 // []string{} for ': xxx' 63 // nil for empty line. 64 outputs []string 65 66 inputs []string 67 orderOnlyInputs []string 68 outputPatterns []pattern 69 isDoubleColon bool 70 isSuffixRule bool 71 cmds []string 72 cmdLineno int 73} 74 75func (r *rule) cmdpos() srcpos { 76 return srcpos{filename: r.filename, lineno: r.cmdLineno} 77} 78 79func isPatternRule(s []byte) (pattern, bool) { 80 i := findLiteralChar(s, '%', 0, noSkipVar) 81 if i < 0 { 82 return pattern{}, false 83 } 84 return pattern{prefix: string(s[:i]), suffix: string(s[i+1:])}, true 85} 86 87func unescapeInput(s []byte) []byte { 88 // only "\ ", "\=" becoms " ", "=" respectively? 89 // other \-escape, such as "\:" keeps "\:". 90 for i := 0; i < len(s); i++ { 91 if s[i] != '\\' { 92 continue 93 } 94 if i+1 < len(s) && s[i+1] == ' ' || s[i+1] == '=' { 95 copy(s[i:], s[i+1:]) 96 s = s[:len(s)-1] 97 } 98 } 99 return s 100} 101 102func unescapeTarget(s []byte) []byte { 103 for i := 0; i < len(s); i++ { 104 if s[i] != '\\' { 105 continue 106 } 107 copy(s[i:], s[i+1:]) 108 s = s[:len(s)-1] 109 } 110 return s 111} 112 113func (r *rule) parseInputs(s []byte) { 114 ws := newWordScanner(s) 115 ws.esc = true 116 add := func(t string) { 117 r.inputs = append(r.inputs, t) 118 } 119 for ws.Scan() { 120 input := ws.Bytes() 121 if len(input) == 1 && input[0] == '|' { 122 add = func(t string) { 123 r.orderOnlyInputs = append(r.orderOnlyInputs, t) 124 } 125 continue 126 } 127 input = unescapeInput(input) 128 if !hasWildcardMetaByte(input) { 129 add(internBytes(input)) 130 continue 131 } 132 m, _ := fsCache.Glob(string(input)) 133 if len(m) == 0 { 134 add(internBytes(input)) 135 continue 136 } 137 for _, t := range m { 138 add(intern(t)) 139 } 140 } 141} 142 143func (r *rule) parseVar(s []byte, rhs expr) (*assignAST, error) { 144 var lhsBytes []byte 145 var op string 146 // TODO(ukai): support override, export. 147 if s[len(s)-1] != '=' { 148 panic(fmt.Sprintf("unexpected lhs %q", s)) 149 } 150 switch s[len(s)-2] { // s[len(s)-1] is '=' 151 case ':': 152 lhsBytes = trimSpaceBytes(s[:len(s)-2]) 153 op = ":=" 154 case '+': 155 lhsBytes = trimSpaceBytes(s[:len(s)-2]) 156 op = "+=" 157 case '?': 158 lhsBytes = trimSpaceBytes(s[:len(s)-2]) 159 op = "?=" 160 default: 161 lhsBytes = trimSpaceBytes(s[:len(s)-1]) 162 op = "=" 163 } 164 assign := &assignAST{ 165 lhs: literal(string(lhsBytes)), 166 rhs: compactExpr(rhs), 167 op: op, 168 } 169 assign.srcpos = r.srcpos 170 return assign, nil 171} 172 173// parse parses rule line. 174// line is rule line until '=', or before ';'. 175// line was already expaned, so probably no need to skip var $(xxx) when 176// finding literal char. i.e. $ is parsed as literal '$'. 177// assign is not nil, if line was known as target specific var '<xxx>: <v>=<val>' 178// rhs is not nil, if line ended with '=' (target specific var after evaluated) 179func (r *rule) parse(line []byte, assign *assignAST, rhs expr) (*assignAST, error) { 180 line = trimLeftSpaceBytes(line) 181 // See semicolon.mk. 182 if rhs == nil && (len(line) == 0 || line[0] == ';') { 183 return nil, nil 184 } 185 r.outputs = []string{} 186 187 index := findLiteralChar(line, ':', 0, noSkipVar) 188 if index < 0 { 189 return nil, errors.New("*** missing separator.") 190 } 191 192 first := line[:index] 193 ws := newWordScanner(first) 194 ws.esc = true 195 pat, isFirstPattern := isPatternRule(first) 196 if isFirstPattern { 197 n := 0 198 for ws.Scan() { 199 n++ 200 if n > 1 { 201 return nil, errors.New("*** mixed implicit and normal rules: deprecated syntax") 202 } 203 } 204 r.outputPatterns = []pattern{pat} 205 } else { 206 for ws.Scan() { 207 // TODO(ukai): expand raw wildcard for output. any usage? 208 r.outputs = append(r.outputs, internBytes(unescapeTarget(ws.Bytes()))) 209 } 210 } 211 212 index++ 213 if index < len(line) && line[index] == ':' { 214 r.isDoubleColon = true 215 index++ 216 } 217 218 rest := line[index:] 219 if assign != nil { 220 if len(rest) > 0 { 221 panic(fmt.Sprintf("pattern specific var? line:%q", line)) 222 } 223 return assign, nil 224 } 225 if rhs != nil { 226 assign, err := r.parseVar(rest, rhs) 227 if err != nil { 228 return nil, err 229 } 230 return assign, nil 231 } 232 index = bytes.IndexByte(rest, ';') 233 if index >= 0 { 234 r.cmds = append(r.cmds, string(rest[index+1:])) 235 rest = rest[:index-1] 236 } 237 index = findLiteralChar(rest, ':', 0, noSkipVar) 238 if index < 0 { 239 r.parseInputs(rest) 240 return nil, nil 241 } 242 243 // %.x: %.y: %.z 244 if isFirstPattern { 245 return nil, errors.New("*** mixed implicit and normal rules: deprecated syntax") 246 } 247 248 second := rest[:index] 249 third := rest[index+1:] 250 251 // r.outputs is already set. 252 ws = newWordScanner(second) 253 if !ws.Scan() { 254 return nil, errors.New("*** missing target pattern.") 255 } 256 outpat, ok := isPatternRule(ws.Bytes()) 257 if !ok { 258 return nil, errors.New("*** target pattern contains no '%'.") 259 } 260 r.outputPatterns = []pattern{outpat} 261 if ws.Scan() { 262 return nil, errors.New("*** multiple target patterns.") 263 } 264 r.parseInputs(third) 265 266 return nil, nil 267} 268