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