| // 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" |
| "fmt" |
| "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 is output of the rule. |
| // []string{} for ': xxx' |
| // nil for empty line. |
| 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, rhs expr) (*assignAST, error) { |
| var lhsBytes []byte |
| var op string |
| // TODO(ukai): support override, export. |
| if s[len(s)-1] != '=' { |
| panic(fmt.Sprintf("unexpected lhs %q", s)) |
| } |
| switch s[len(s)-2] { // s[len(s)-1] is '=' |
| case ':': |
| lhsBytes = trimSpaceBytes(s[:len(s)-2]) |
| op = ":=" |
| case '+': |
| lhsBytes = trimSpaceBytes(s[:len(s)-2]) |
| op = "+=" |
| case '?': |
| lhsBytes = trimSpaceBytes(s[:len(s)-2]) |
| op = "?=" |
| default: |
| lhsBytes = trimSpaceBytes(s[:len(s)-1]) |
| op = "=" |
| } |
| lhs, _, err := parseExpr(lhsBytes, nil, parseOp{alloc: true}) |
| if err != nil { |
| return nil, err |
| } |
| assign := &assignAST{ |
| lhs: lhs, |
| rhs: compactExpr(rhs), |
| op: op, |
| } |
| assign.srcpos = r.srcpos |
| return assign, nil |
| } |
| |
| // parse parses rule line. |
| // line is rule line until '=', or before ';' |
| // rhs is not nil, if line ended with '=' (target specific var) |
| func (r *rule) parse(line []byte, rhs expr) (*assignAST, error) { |
| var removed bool |
| line, removed = removeComment(line) |
| if removed { |
| rhs = nil |
| line = trimRightSpaceBytes(line) |
| } |
| line = trimLeftSpaceBytes(line) |
| // See semicolon.mk. |
| if rhs == nil && (len(line) == 0 || line[0] == ';') { |
| return nil, nil |
| } |
| r.outputs = []string{} |
| |
| 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:] |
| if rhs != nil { |
| assign, err := r.parseVar(rest, rhs) |
| if err != nil { |
| return nil, err |
| } |
| 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 |
| } |