// Copyright 2015 Google Inc. All rights reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kati import ( "bytes" "errors" "strings" ) type pattern struct { prefix, suffix string } func (p pattern) String() string { return p.prefix + "%" + p.suffix } func (p pattern) match(s string) bool { return strings.HasPrefix(s, p.prefix) && strings.HasSuffix(s, p.suffix) } func (p pattern) subst(repl, str string) string { in := str trimed := str if p.prefix != "" { trimed = strings.TrimPrefix(in, p.prefix) if trimed == in { return str } } in = trimed if p.suffix != "" { trimed = strings.TrimSuffix(in, p.suffix) if trimed == in { return str } } rs := strings.SplitN(repl, "%", 2) if len(rs) != 2 { return repl } return rs[0] + trimed + rs[1] } type rule struct { srcpos outputs []string inputs []string orderOnlyInputs []string outputPatterns []pattern isDoubleColon bool isSuffixRule bool cmds []string cmdLineno int } func (r *rule) cmdpos() srcpos { return srcpos{filename: r.filename, lineno: r.cmdLineno} } func isPatternRule(s []byte) (pattern, bool) { i := bytes.IndexByte(s, '%') if i < 0 { return pattern{}, false } return pattern{prefix: string(s[:i]), suffix: string(s[i+1:])}, true } func (r *rule) parseInputs(s []byte) { ws := newWordScanner(s) isOrderOnly := false for ws.Scan() { input := ws.Bytes() if len(input) == 1 && input[0] == '|' { isOrderOnly = true continue } if isOrderOnly { r.orderOnlyInputs = append(r.orderOnlyInputs, internBytes(input)) } else { r.inputs = append(r.inputs, internBytes(input)) } } } func (r *rule) parseVar(s []byte) (*assignAST, error) { eq := bytes.IndexByte(s, '=') if eq <= 0 { return nil, nil } rhs := trimLeftSpaceBytes(s[eq+1:]) var lhs []byte var op string // TODO(ukai): support override, export. switch s[eq-1] { // s[eq] is '=' case ':': lhs = trimSpaceBytes(s[:eq-1]) op = ":=" case '+': lhs = trimSpaceBytes(s[:eq-1]) op = "+=" case '?': lhs = trimSpaceBytes(s[:eq-1]) op = "?=" default: lhs = trimSpaceBytes(s[:eq]) op = "=" } assign, err := newAssignAST(nil, lhs, rhs, op) if err != nil { return nil, err } assign.srcpos = r.srcpos return assign, nil } func (r *rule) parse(line []byte) (*assignAST, error) { index := bytes.IndexByte(line, ':') if index < 0 { return nil, errors.New("*** missing separator.") } first := line[:index] ws := newWordScanner(first) pat, isFirstPattern := isPatternRule(first) if isFirstPattern { n := 0 for ws.Scan() { n++ if n > 1 { return nil, errors.New("*** mixed implicit and normal rules: deprecated syntax") } } r.outputPatterns = []pattern{pat} } else { for ws.Scan() { r.outputs = append(r.outputs, internBytes(ws.Bytes())) } } index++ if index < len(line) && line[index] == ':' { r.isDoubleColon = true index++ } rest := line[index:] assign, err := r.parseVar(rest) if err != nil { return nil, err } if assign != nil { return assign, nil } index = bytes.IndexByte(rest, ':') if index < 0 { r.parseInputs(rest) return nil, nil } // %.x: %.y: %.z if isFirstPattern { return nil, errors.New("*** mixed implicit and normal rules: deprecated syntax") } second := rest[:index] third := rest[index+1:] // r.outputs is already set. ws = newWordScanner(second) if !ws.Scan() { return nil, errors.New("*** missing target pattern.") } outpat, ok := isPatternRule(ws.Bytes()) if !ok { return nil, errors.New("*** target pattern contains no '%'.") } r.outputPatterns = []pattern{outpat} if ws.Scan() { return nil, errors.New("*** multiple target patterns.") } r.parseInputs(third) return nil, nil }