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 "errors" 19 "fmt" 20 "io" 21 "strings" 22 "time" 23) 24 25var shBuiltins = []struct { 26 name string 27 pattern expr 28 compact func(*funcShell, []Value) Value 29}{ 30 { 31 name: "android:rot13", 32 // in repo/android/build/core/definisions.mk 33 // echo $(1) | tr 'a-zA-Z' 'n-za-mN-ZA-M' 34 pattern: expr{ 35 literal("echo "), 36 matchVarref{}, 37 literal(" | tr 'a-zA-Z' 'n-za-mN-ZA-M'"), 38 }, 39 compact: func(sh *funcShell, matches []Value) Value { 40 return &funcShellAndroidRot13{ 41 funcShell: sh, 42 v: matches[0], 43 } 44 }, 45 }, 46 { 47 name: "shell-date", 48 pattern: expr{ 49 mustLiteralRE(`date \+(\S+)`), 50 }, 51 compact: compactShellDate, 52 }, 53 { 54 name: "shell-date-quoted", 55 pattern: expr{ 56 mustLiteralRE(`date "\+([^"]+)"`), 57 }, 58 compact: compactShellDate, 59 }, 60} 61 62type funcShellAndroidRot13 struct { 63 *funcShell 64 v Value 65} 66 67func rot13(buf []byte) { 68 for i, b := range buf { 69 // tr 'a-zA-Z' 'n-za-mN-ZA-M' 70 if b >= 'a' && b <= 'z' { 71 b += 'n' - 'a' 72 if b > 'z' { 73 b -= 'z' - 'a' + 1 74 } 75 } else if b >= 'A' && b <= 'Z' { 76 b += 'N' - 'A' 77 if b > 'Z' { 78 b -= 'Z' - 'A' + 1 79 } 80 } 81 buf[i] = b 82 } 83} 84 85func (f *funcShellAndroidRot13) Eval(w evalWriter, ev *Evaluator) error { 86 abuf := newEbuf() 87 fargs, err := ev.args(abuf, f.v) 88 if err != nil { 89 return err 90 } 91 rot13(fargs[0]) 92 w.Write(fargs[0]) 93 abuf.release() 94 return nil 95} 96 97var ( 98 // ShellDateTimestamp is an timestamp used for $(shell date). 99 ShellDateTimestamp time.Time 100 shellDateFormatRef = map[string]string{ 101 "%Y": "2006", 102 "%m": "01", 103 "%d": "02", 104 "%H": "15", 105 "%M": "04", 106 "%S": "05", 107 "%b": "Jan", 108 "%k": "15", // XXX 109 } 110) 111 112type funcShellDate struct { 113 *funcShell 114 format string 115} 116 117func compactShellDate(sh *funcShell, v []Value) Value { 118 if ShellDateTimestamp.IsZero() { 119 return sh 120 } 121 tf, ok := v[0].(literal) 122 if !ok { 123 return sh 124 } 125 tfstr := string(tf) 126 for k, v := range shellDateFormatRef { 127 tfstr = strings.Replace(tfstr, k, v, -1) 128 } 129 return &funcShellDate{ 130 funcShell: sh, 131 format: tfstr, 132 } 133} 134 135func (f *funcShellDate) Eval(w evalWriter, ev *Evaluator) error { 136 fmt.Fprint(w, ShellDateTimestamp.Format(f.format)) 137 return nil 138} 139 140type buildinCommand interface { 141 run(w evalWriter) 142} 143 144var errFindEmulatorDisabled = errors.New("builtin: find emulator disabled") 145 146func parseBuiltinCommand(cmd string) (buildinCommand, error) { 147 if !UseFindEmulator { 148 return nil, errFindEmulatorDisabled 149 } 150 if strings.HasPrefix(trimLeftSpace(cmd), "build/tools/findleaves") { 151 return parseFindleavesCommand(cmd) 152 } 153 return parseFindCommand(cmd) 154} 155 156type shellParser struct { 157 cmd string 158 ungetToken string 159} 160 161func (p *shellParser) token() (string, error) { 162 if p.ungetToken != "" { 163 tok := p.ungetToken 164 p.ungetToken = "" 165 return tok, nil 166 } 167 p.cmd = trimLeftSpace(p.cmd) 168 if len(p.cmd) == 0 { 169 return "", io.EOF 170 } 171 if p.cmd[0] == ';' { 172 tok := p.cmd[0:1] 173 p.cmd = p.cmd[1:] 174 return tok, nil 175 } 176 if p.cmd[0] == '&' { 177 if len(p.cmd) == 1 || p.cmd[1] != '&' { 178 return "", errFindBackground 179 } 180 tok := p.cmd[0:2] 181 p.cmd = p.cmd[2:] 182 return tok, nil 183 } 184 // TODO(ukai): redirect token. 185 i := 0 186 for i < len(p.cmd) { 187 if isWhitespace(rune(p.cmd[i])) || p.cmd[i] == ';' || p.cmd[i] == '&' { 188 break 189 } 190 i++ 191 } 192 tok := p.cmd[0:i] 193 p.cmd = p.cmd[i:] 194 c := tok[0] 195 if c == '\'' || c == '"' { 196 if len(tok) < 2 || tok[len(tok)-1] != c { 197 return "", errFindUnbalancedQuote 198 } 199 // todo: unquote? 200 tok = tok[1 : len(tok)-1] 201 } 202 return tok, nil 203} 204 205func (p *shellParser) unget(s string) { 206 if s != "" { 207 p.ungetToken = s 208 } 209} 210 211func (p *shellParser) expect(toks ...string) error { 212 tok, err := p.token() 213 if err != nil { 214 return err 215 } 216 for _, t := range toks { 217 if tok == t { 218 return nil 219 } 220 } 221 return fmt.Errorf("shell: token=%q; want=%q", tok, toks) 222} 223 224func (p *shellParser) expectSeq(toks ...string) error { 225 for _, tok := range toks { 226 err := p.expect(tok) 227 if err != nil { 228 return err 229 } 230 } 231 return nil 232} 233