Home | History | Annotate | Download | only in kati
      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 
     15 package kati
     16 
     17 import (
     18 	"errors"
     19 	"fmt"
     20 	"io"
     21 	"strings"
     22 	"time"
     23 )
     24 
     25 var 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 
     62 type funcShellAndroidRot13 struct {
     63 	*funcShell
     64 	v Value
     65 }
     66 
     67 func 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 
     85 func (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 
     97 var (
     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 
    112 type funcShellDate struct {
    113 	*funcShell
    114 	format string
    115 }
    116 
    117 func 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 
    135 func (f *funcShellDate) Eval(w evalWriter, ev *Evaluator) error {
    136 	fmt.Fprint(w, ShellDateTimestamp.Format(f.format))
    137 	return nil
    138 }
    139 
    140 type buildinCommand interface {
    141 	run(w evalWriter)
    142 }
    143 
    144 var errFindEmulatorDisabled = errors.New("builtin: find emulator disabled")
    145 
    146 func 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 
    156 type shellParser struct {
    157 	cmd        string
    158 	ungetToken string
    159 }
    160 
    161 func (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 
    205 func (p *shellParser) unget(s string) {
    206 	if s != "" {
    207 		p.ungetToken = s
    208 	}
    209 }
    210 
    211 func (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 
    224 func (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