You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
232 lines
4.4 KiB
232 lines
4.4 KiB
// 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 ( |
|
"errors" |
|
"fmt" |
|
"io" |
|
"strings" |
|
"time" |
|
) |
|
|
|
var shBuiltins = []struct { |
|
name string |
|
pattern expr |
|
compact func(*funcShell, []Value) Value |
|
}{ |
|
{ |
|
name: "android:rot13", |
|
// in repo/android/build/core/definisions.mk |
|
// echo $(1) | tr 'a-zA-Z' 'n-za-mN-ZA-M' |
|
pattern: expr{ |
|
literal("echo "), |
|
matchVarref{}, |
|
literal(" | tr 'a-zA-Z' 'n-za-mN-ZA-M'"), |
|
}, |
|
compact: func(sh *funcShell, matches []Value) Value { |
|
return &funcShellAndroidRot13{ |
|
funcShell: sh, |
|
v: matches[0], |
|
} |
|
}, |
|
}, |
|
{ |
|
name: "shell-date", |
|
pattern: expr{ |
|
mustLiteralRE(`date \+(\S+)`), |
|
}, |
|
compact: compactShellDate, |
|
}, |
|
{ |
|
name: "shell-date-quoted", |
|
pattern: expr{ |
|
mustLiteralRE(`date "\+([^"]+)"`), |
|
}, |
|
compact: compactShellDate, |
|
}, |
|
} |
|
|
|
type funcShellAndroidRot13 struct { |
|
*funcShell |
|
v Value |
|
} |
|
|
|
func rot13(buf []byte) { |
|
for i, b := range buf { |
|
// tr 'a-zA-Z' 'n-za-mN-ZA-M' |
|
if b >= 'a' && b <= 'z' { |
|
b += 'n' - 'a' |
|
if b > 'z' { |
|
b -= 'z' - 'a' + 1 |
|
} |
|
} else if b >= 'A' && b <= 'Z' { |
|
b += 'N' - 'A' |
|
if b > 'Z' { |
|
b -= 'Z' - 'A' + 1 |
|
} |
|
} |
|
buf[i] = b |
|
} |
|
} |
|
|
|
func (f *funcShellAndroidRot13) Eval(w evalWriter, ev *Evaluator) error { |
|
abuf := newEbuf() |
|
fargs, err := ev.args(abuf, f.v) |
|
if err != nil { |
|
return err |
|
} |
|
rot13(fargs[0]) |
|
w.Write(fargs[0]) |
|
abuf.release() |
|
return nil |
|
} |
|
|
|
var ( |
|
// ShellDateTimestamp is an timestamp used for $(shell date). |
|
ShellDateTimestamp time.Time |
|
shellDateFormatRef = map[string]string{ |
|
"%Y": "2006", |
|
"%m": "01", |
|
"%d": "02", |
|
"%H": "15", |
|
"%M": "04", |
|
"%S": "05", |
|
"%b": "Jan", |
|
"%k": "15", // XXX |
|
} |
|
) |
|
|
|
type funcShellDate struct { |
|
*funcShell |
|
format string |
|
} |
|
|
|
func compactShellDate(sh *funcShell, v []Value) Value { |
|
if ShellDateTimestamp.IsZero() { |
|
return sh |
|
} |
|
tf, ok := v[0].(literal) |
|
if !ok { |
|
return sh |
|
} |
|
tfstr := string(tf) |
|
for k, v := range shellDateFormatRef { |
|
tfstr = strings.Replace(tfstr, k, v, -1) |
|
} |
|
return &funcShellDate{ |
|
funcShell: sh, |
|
format: tfstr, |
|
} |
|
} |
|
|
|
func (f *funcShellDate) Eval(w evalWriter, ev *Evaluator) error { |
|
fmt.Fprint(w, ShellDateTimestamp.Format(f.format)) |
|
return nil |
|
} |
|
|
|
type buildinCommand interface { |
|
run(w evalWriter) |
|
} |
|
|
|
var errFindEmulatorDisabled = errors.New("builtin: find emulator disabled") |
|
|
|
func parseBuiltinCommand(cmd string) (buildinCommand, error) { |
|
if !UseFindEmulator { |
|
return nil, errFindEmulatorDisabled |
|
} |
|
if strings.HasPrefix(trimLeftSpace(cmd), "build/tools/findleaves") { |
|
return parseFindleavesCommand(cmd) |
|
} |
|
return parseFindCommand(cmd) |
|
} |
|
|
|
type shellParser struct { |
|
cmd string |
|
ungetToken string |
|
} |
|
|
|
func (p *shellParser) token() (string, error) { |
|
if p.ungetToken != "" { |
|
tok := p.ungetToken |
|
p.ungetToken = "" |
|
return tok, nil |
|
} |
|
p.cmd = trimLeftSpace(p.cmd) |
|
if len(p.cmd) == 0 { |
|
return "", io.EOF |
|
} |
|
if p.cmd[0] == ';' { |
|
tok := p.cmd[0:1] |
|
p.cmd = p.cmd[1:] |
|
return tok, nil |
|
} |
|
if p.cmd[0] == '&' { |
|
if len(p.cmd) == 1 || p.cmd[1] != '&' { |
|
return "", errFindBackground |
|
} |
|
tok := p.cmd[0:2] |
|
p.cmd = p.cmd[2:] |
|
return tok, nil |
|
} |
|
// TODO(ukai): redirect token. |
|
i := 0 |
|
for i < len(p.cmd) { |
|
if isWhitespace(rune(p.cmd[i])) || p.cmd[i] == ';' || p.cmd[i] == '&' { |
|
break |
|
} |
|
i++ |
|
} |
|
tok := p.cmd[0:i] |
|
p.cmd = p.cmd[i:] |
|
c := tok[0] |
|
if c == '\'' || c == '"' { |
|
if len(tok) < 2 || tok[len(tok)-1] != c { |
|
return "", errFindUnbalancedQuote |
|
} |
|
// todo: unquote? |
|
tok = tok[1 : len(tok)-1] |
|
} |
|
return tok, nil |
|
} |
|
|
|
func (p *shellParser) unget(s string) { |
|
if s != "" { |
|
p.ungetToken = s |
|
} |
|
} |
|
|
|
func (p *shellParser) expect(toks ...string) error { |
|
tok, err := p.token() |
|
if err != nil { |
|
return err |
|
} |
|
for _, t := range toks { |
|
if tok == t { |
|
return nil |
|
} |
|
} |
|
return fmt.Errorf("shell: token=%q; want=%q", tok, toks) |
|
} |
|
|
|
func (p *shellParser) expectSeq(toks ...string) error { |
|
for _, tok := range toks { |
|
err := p.expect(tok) |
|
if err != nil { |
|
return err |
|
} |
|
} |
|
return nil |
|
}
|
|
|