Shinichiro Hamaji | b69bf8a | 2015-06-10 14:52:06 +0900 | [diff] [blame] | 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 | |
Fumitoshi Ukai | 744bb2b | 2015-06-25 00:10:52 +0900 | [diff] [blame] | 15 | package kati |
Shinichiro Hamaji | 0493261 | 2015-03-31 23:46:56 +0900 | [diff] [blame] | 16 | |
| 17 | import ( |
Fumitoshi Ukai | 0b9e813 | 2015-04-30 10:20:18 +0900 | [diff] [blame] | 18 | "bytes" |
Fumitoshi Ukai | 953ce6f | 2015-04-04 00:38:53 +0900 | [diff] [blame] | 19 | "errors" |
Fumitoshi Ukai | b97be67 | 2015-07-02 15:12:48 +0900 | [diff] [blame] | 20 | "fmt" |
Shinichiro Hamaji | 0493261 | 2015-03-31 23:46:56 +0900 | [diff] [blame] | 21 | "strings" |
| 22 | ) |
| 23 | |
Fumitoshi Ukai | 935de96 | 2015-04-28 17:08:20 +0900 | [diff] [blame] | 24 | type pattern struct { |
| 25 | prefix, suffix string |
| 26 | } |
| 27 | |
| 28 | func (p pattern) String() string { |
| 29 | return p.prefix + "%" + p.suffix |
| 30 | } |
| 31 | |
| 32 | func (p pattern) match(s string) bool { |
| 33 | return strings.HasPrefix(s, p.prefix) && strings.HasSuffix(s, p.suffix) |
| 34 | } |
| 35 | |
| 36 | func (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 | |
Fumitoshi Ukai | adc1444 | 2015-06-25 16:10:30 +0900 | [diff] [blame] | 59 | type rule struct { |
Fumitoshi Ukai | 65c7233 | 2015-06-26 21:32:50 +0900 | [diff] [blame] | 60 | srcpos |
Fumitoshi Ukai | b97be67 | 2015-07-02 15:12:48 +0900 | [diff] [blame] | 61 | // outputs is output of the rule. |
| 62 | // []string{} for ': xxx' |
| 63 | // nil for empty line. |
| 64 | outputs []string |
| 65 | |
Shinichiro Hamaji | 5c53b57 | 2015-04-02 05:36:42 +0900 | [diff] [blame] | 66 | inputs []string |
| 67 | orderOnlyInputs []string |
Fumitoshi Ukai | 935de96 | 2015-04-28 17:08:20 +0900 | [diff] [blame] | 68 | outputPatterns []pattern |
Shinichiro Hamaji | 5c53b57 | 2015-04-02 05:36:42 +0900 | [diff] [blame] | 69 | isDoubleColon bool |
| 70 | isSuffixRule bool |
| 71 | cmds []string |
Shinichiro Hamaji | 5c53b57 | 2015-04-02 05:36:42 +0900 | [diff] [blame] | 72 | cmdLineno int |
Shinichiro Hamaji | 0493261 | 2015-03-31 23:46:56 +0900 | [diff] [blame] | 73 | } |
| 74 | |
Fumitoshi Ukai | 65c7233 | 2015-06-26 21:32:50 +0900 | [diff] [blame] | 75 | func (r *rule) cmdpos() srcpos { |
| 76 | return srcpos{filename: r.filename, lineno: r.cmdLineno} |
| 77 | } |
| 78 | |
Fumitoshi Ukai | 0b9e813 | 2015-04-30 10:20:18 +0900 | [diff] [blame] | 79 | func isPatternRule(s []byte) (pattern, bool) { |
Fumitoshi Ukai | 6addc2f | 2015-07-22 11:33:35 +0900 | [diff] [blame] | 80 | i := findLiteralChar(s, '%', 0, noSkipVar) |
Fumitoshi Ukai | 935de96 | 2015-04-28 17:08:20 +0900 | [diff] [blame] | 81 | if i < 0 { |
| 82 | return pattern{}, false |
| 83 | } |
Fumitoshi Ukai | 0b9e813 | 2015-04-30 10:20:18 +0900 | [diff] [blame] | 84 | return pattern{prefix: string(s[:i]), suffix: string(s[i+1:])}, true |
Shinichiro Hamaji | 21a9b5f | 2015-04-01 02:42:59 +0900 | [diff] [blame] | 85 | } |
| 86 | |
Fumitoshi Ukai | 4b3a7dd | 2015-07-21 13:07:44 +0900 | [diff] [blame] | 87 | func 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 | |
Fumitoshi Ukai | 7c8b0ae | 2015-07-22 11:11:23 +0900 | [diff] [blame] | 102 | func 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 | |
Fumitoshi Ukai | adc1444 | 2015-06-25 16:10:30 +0900 | [diff] [blame] | 113 | func (r *rule) parseInputs(s []byte) { |
Fumitoshi Ukai | 9da19f6 | 2015-05-08 14:39:08 +0900 | [diff] [blame] | 114 | ws := newWordScanner(s) |
Fumitoshi Ukai | 4b3a7dd | 2015-07-21 13:07:44 +0900 | [diff] [blame] | 115 | ws.esc = true |
Fumitoshi Ukai | 9c2f98c | 2015-07-22 16:46:08 +0900 | [diff] [blame] | 116 | add := func(t string) { |
| 117 | r.inputs = append(r.inputs, t) |
| 118 | } |
Fumitoshi Ukai | 9da19f6 | 2015-05-08 14:39:08 +0900 | [diff] [blame] | 119 | for ws.Scan() { |
| 120 | input := ws.Bytes() |
Fumitoshi Ukai | 0b9e813 | 2015-04-30 10:20:18 +0900 | [diff] [blame] | 121 | if len(input) == 1 && input[0] == '|' { |
Fumitoshi Ukai | 9c2f98c | 2015-07-22 16:46:08 +0900 | [diff] [blame] | 122 | add = func(t string) { |
| 123 | r.orderOnlyInputs = append(r.orderOnlyInputs, t) |
| 124 | } |
Shinichiro Hamaji | 5c53b57 | 2015-04-02 05:36:42 +0900 | [diff] [blame] | 125 | continue |
| 126 | } |
Fumitoshi Ukai | 4b3a7dd | 2015-07-21 13:07:44 +0900 | [diff] [blame] | 127 | input = unescapeInput(input) |
Fumitoshi Ukai | 9c2f98c | 2015-07-22 16:46:08 +0900 | [diff] [blame] | 128 | if !hasWildcardMetaByte(input) { |
| 129 | add(internBytes(input)) |
| 130 | continue |
| 131 | } |
Fumitoshi Ukai | 0547db6 | 2015-07-29 16:20:59 +0900 | [diff] [blame] | 132 | m, _ := fsCache.Glob(string(input)) |
Fumitoshi Ukai | 9c2f98c | 2015-07-22 16:46:08 +0900 | [diff] [blame] | 133 | if len(m) == 0 { |
| 134 | add(internBytes(input)) |
| 135 | continue |
| 136 | } |
| 137 | for _, t := range m { |
| 138 | add(intern(t)) |
Shinichiro Hamaji | 5c53b57 | 2015-04-02 05:36:42 +0900 | [diff] [blame] | 139 | } |
| 140 | } |
| 141 | } |
| 142 | |
Fumitoshi Ukai | b97be67 | 2015-07-02 15:12:48 +0900 | [diff] [blame] | 143 | func (r *rule) parseVar(s []byte, rhs expr) (*assignAST, error) { |
| 144 | var lhsBytes []byte |
Shinichiro Hamaji | 7825b65 | 2015-06-04 13:47:14 +0900 | [diff] [blame] | 145 | var op string |
Fumitoshi Ukai | 953ce6f | 2015-04-04 00:38:53 +0900 | [diff] [blame] | 146 | // TODO(ukai): support override, export. |
Fumitoshi Ukai | b97be67 | 2015-07-02 15:12:48 +0900 | [diff] [blame] | 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 '=' |
Fumitoshi Ukai | 0b9e813 | 2015-04-30 10:20:18 +0900 | [diff] [blame] | 151 | case ':': |
Fumitoshi Ukai | b97be67 | 2015-07-02 15:12:48 +0900 | [diff] [blame] | 152 | lhsBytes = trimSpaceBytes(s[:len(s)-2]) |
Shinichiro Hamaji | 7825b65 | 2015-06-04 13:47:14 +0900 | [diff] [blame] | 153 | op = ":=" |
Fumitoshi Ukai | 0b9e813 | 2015-04-30 10:20:18 +0900 | [diff] [blame] | 154 | case '+': |
Fumitoshi Ukai | b97be67 | 2015-07-02 15:12:48 +0900 | [diff] [blame] | 155 | lhsBytes = trimSpaceBytes(s[:len(s)-2]) |
Shinichiro Hamaji | 7825b65 | 2015-06-04 13:47:14 +0900 | [diff] [blame] | 156 | op = "+=" |
Fumitoshi Ukai | 0b9e813 | 2015-04-30 10:20:18 +0900 | [diff] [blame] | 157 | case '?': |
Fumitoshi Ukai | b97be67 | 2015-07-02 15:12:48 +0900 | [diff] [blame] | 158 | lhsBytes = trimSpaceBytes(s[:len(s)-2]) |
Shinichiro Hamaji | 7825b65 | 2015-06-04 13:47:14 +0900 | [diff] [blame] | 159 | op = "?=" |
Fumitoshi Ukai | 953ce6f | 2015-04-04 00:38:53 +0900 | [diff] [blame] | 160 | default: |
Fumitoshi Ukai | b97be67 | 2015-07-02 15:12:48 +0900 | [diff] [blame] | 161 | lhsBytes = trimSpaceBytes(s[:len(s)-1]) |
Shinichiro Hamaji | 7825b65 | 2015-06-04 13:47:14 +0900 | [diff] [blame] | 162 | op = "=" |
Fumitoshi Ukai | 953ce6f | 2015-04-04 00:38:53 +0900 | [diff] [blame] | 163 | } |
Fumitoshi Ukai | b97be67 | 2015-07-02 15:12:48 +0900 | [diff] [blame] | 164 | assign := &assignAST{ |
Fumitoshi Ukai | 91608db | 2015-07-07 11:52:56 +0900 | [diff] [blame] | 165 | lhs: literal(string(lhsBytes)), |
Fumitoshi Ukai | b97be67 | 2015-07-02 15:12:48 +0900 | [diff] [blame] | 166 | rhs: compactExpr(rhs), |
| 167 | op: op, |
| 168 | } |
Fumitoshi Ukai | 65c7233 | 2015-06-26 21:32:50 +0900 | [diff] [blame] | 169 | assign.srcpos = r.srcpos |
| 170 | return assign, nil |
Fumitoshi Ukai | 953ce6f | 2015-04-04 00:38:53 +0900 | [diff] [blame] | 171 | } |
| 172 | |
Fumitoshi Ukai | b97be67 | 2015-07-02 15:12:48 +0900 | [diff] [blame] | 173 | // parse parses rule line. |
Fumitoshi Ukai | 6addc2f | 2015-07-22 11:33:35 +0900 | [diff] [blame] | 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 '$'. |
Fumitoshi Ukai | 201df42 | 2015-07-07 17:31:05 +0900 | [diff] [blame] | 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) |
| 179 | func (r *rule) parse(line []byte, assign *assignAST, rhs expr) (*assignAST, error) { |
Fumitoshi Ukai | b97be67 | 2015-07-02 15:12:48 +0900 | [diff] [blame] | 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 | |
Fumitoshi Ukai | 6addc2f | 2015-07-22 11:33:35 +0900 | [diff] [blame] | 187 | index := findLiteralChar(line, ':', 0, noSkipVar) |
Shinichiro Hamaji | 21a9b5f | 2015-04-01 02:42:59 +0900 | [diff] [blame] | 188 | if index < 0 { |
Fumitoshi Ukai | 953ce6f | 2015-04-04 00:38:53 +0900 | [diff] [blame] | 189 | return nil, errors.New("*** missing separator.") |
Shinichiro Hamaji | 0493261 | 2015-03-31 23:46:56 +0900 | [diff] [blame] | 190 | } |
| 191 | |
Shinichiro Hamaji | 21a9b5f | 2015-04-01 02:42:59 +0900 | [diff] [blame] | 192 | first := line[:index] |
Fumitoshi Ukai | 9da19f6 | 2015-05-08 14:39:08 +0900 | [diff] [blame] | 193 | ws := newWordScanner(first) |
Fumitoshi Ukai | 7c8b0ae | 2015-07-22 11:11:23 +0900 | [diff] [blame] | 194 | ws.esc = true |
Fumitoshi Ukai | 935de96 | 2015-04-28 17:08:20 +0900 | [diff] [blame] | 195 | pat, isFirstPattern := isPatternRule(first) |
Shinichiro Hamaji | 21a9b5f | 2015-04-01 02:42:59 +0900 | [diff] [blame] | 196 | if isFirstPattern { |
Fumitoshi Ukai | 9da19f6 | 2015-05-08 14:39:08 +0900 | [diff] [blame] | 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 | } |
Shinichiro Hamaji | 21a9b5f | 2015-04-01 02:42:59 +0900 | [diff] [blame] | 203 | } |
Fumitoshi Ukai | 935de96 | 2015-04-28 17:08:20 +0900 | [diff] [blame] | 204 | r.outputPatterns = []pattern{pat} |
Shinichiro Hamaji | 21a9b5f | 2015-04-01 02:42:59 +0900 | [diff] [blame] | 205 | } else { |
Fumitoshi Ukai | 9da19f6 | 2015-05-08 14:39:08 +0900 | [diff] [blame] | 206 | for ws.Scan() { |
Fumitoshi Ukai | 9c2f98c | 2015-07-22 16:46:08 +0900 | [diff] [blame] | 207 | // TODO(ukai): expand raw wildcard for output. any usage? |
Fumitoshi Ukai | 7c8b0ae | 2015-07-22 11:11:23 +0900 | [diff] [blame] | 208 | r.outputs = append(r.outputs, internBytes(unescapeTarget(ws.Bytes()))) |
Fumitoshi Ukai | 0b9e813 | 2015-04-30 10:20:18 +0900 | [diff] [blame] | 209 | } |
Shinichiro Hamaji | 21a9b5f | 2015-04-01 02:42:59 +0900 | [diff] [blame] | 210 | } |
| 211 | |
| 212 | index++ |
| 213 | if index < len(line) && line[index] == ':' { |
| 214 | r.isDoubleColon = true |
| 215 | index++ |
| 216 | } |
| 217 | |
| 218 | rest := line[index:] |
Fumitoshi Ukai | 201df42 | 2015-07-07 17:31:05 +0900 | [diff] [blame] | 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 | } |
Fumitoshi Ukai | b97be67 | 2015-07-02 15:12:48 +0900 | [diff] [blame] | 225 | if rhs != nil { |
| 226 | assign, err := r.parseVar(rest, rhs) |
| 227 | if err != nil { |
| 228 | return nil, err |
| 229 | } |
Fumitoshi Ukai | 953ce6f | 2015-04-04 00:38:53 +0900 | [diff] [blame] | 230 | return assign, nil |
| 231 | } |
Fumitoshi Ukai | 8f4be41 | 2015-07-07 11:57:13 +0900 | [diff] [blame] | 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 | } |
Fumitoshi Ukai | 6addc2f | 2015-07-22 11:33:35 +0900 | [diff] [blame] | 237 | index = findLiteralChar(rest, ':', 0, noSkipVar) |
Shinichiro Hamaji | 21a9b5f | 2015-04-01 02:42:59 +0900 | [diff] [blame] | 238 | if index < 0 { |
Shinichiro Hamaji | 5c53b57 | 2015-04-02 05:36:42 +0900 | [diff] [blame] | 239 | r.parseInputs(rest) |
Fumitoshi Ukai | 953ce6f | 2015-04-04 00:38:53 +0900 | [diff] [blame] | 240 | return nil, nil |
Shinichiro Hamaji | 21a9b5f | 2015-04-01 02:42:59 +0900 | [diff] [blame] | 241 | } |
| 242 | |
| 243 | // %.x: %.y: %.z |
| 244 | if isFirstPattern { |
Fumitoshi Ukai | 953ce6f | 2015-04-04 00:38:53 +0900 | [diff] [blame] | 245 | return nil, errors.New("*** mixed implicit and normal rules: deprecated syntax") |
Shinichiro Hamaji | 21a9b5f | 2015-04-01 02:42:59 +0900 | [diff] [blame] | 246 | } |
| 247 | |
| 248 | second := rest[:index] |
| 249 | third := rest[index+1:] |
| 250 | |
Fumitoshi Ukai | 0b9e813 | 2015-04-30 10:20:18 +0900 | [diff] [blame] | 251 | // r.outputs is already set. |
Fumitoshi Ukai | 9da19f6 | 2015-05-08 14:39:08 +0900 | [diff] [blame] | 252 | ws = newWordScanner(second) |
| 253 | if !ws.Scan() { |
Fumitoshi Ukai | 953ce6f | 2015-04-04 00:38:53 +0900 | [diff] [blame] | 254 | return nil, errors.New("*** missing target pattern.") |
Shinichiro Hamaji | 21a9b5f | 2015-04-01 02:42:59 +0900 | [diff] [blame] | 255 | } |
Fumitoshi Ukai | 9da19f6 | 2015-05-08 14:39:08 +0900 | [diff] [blame] | 256 | outpat, ok := isPatternRule(ws.Bytes()) |
Fumitoshi Ukai | 935de96 | 2015-04-28 17:08:20 +0900 | [diff] [blame] | 257 | if !ok { |
Fumitoshi Ukai | 953ce6f | 2015-04-04 00:38:53 +0900 | [diff] [blame] | 258 | return nil, errors.New("*** target pattern contains no '%'.") |
Shinichiro Hamaji | 21a9b5f | 2015-04-01 02:42:59 +0900 | [diff] [blame] | 259 | } |
Fumitoshi Ukai | 935de96 | 2015-04-28 17:08:20 +0900 | [diff] [blame] | 260 | r.outputPatterns = []pattern{outpat} |
Fumitoshi Ukai | 9da19f6 | 2015-05-08 14:39:08 +0900 | [diff] [blame] | 261 | if ws.Scan() { |
| 262 | return nil, errors.New("*** multiple target patterns.") |
| 263 | } |
Shinichiro Hamaji | 5c53b57 | 2015-04-02 05:36:42 +0900 | [diff] [blame] | 264 | r.parseInputs(third) |
Shinichiro Hamaji | 21a9b5f | 2015-04-01 02:42:59 +0900 | [diff] [blame] | 265 | |
Fumitoshi Ukai | 953ce6f | 2015-04-04 00:38:53 +0900 | [diff] [blame] | 266 | return nil, nil |
Shinichiro Hamaji | 0493261 | 2015-03-31 23:46:56 +0900 | [diff] [blame] | 267 | } |