blob: a248325a9a0ab6e4b1fe781b68cf44b32b0d3822 [file] [log] [blame]
// 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 (
"bytes"
"errors"
"fmt"
"io"
"regexp"
"strconv"
"strings"
"sync"
)
var (
errEndOfInput = errors.New("parse: unexpected end of input")
errNotLiteral = errors.New("valueNum: not literal")
bufFree = sync.Pool{
New: func() interface{} { return new(buffer) },
}
)
type buffer struct {
bytes.Buffer
args [][]byte
}
func newBuf() *buffer {
buf := bufFree.Get().(*buffer)
return buf
}
func freeBuf(buf *buffer) {
if cap(buf.Bytes()) > 1024 {
return
}
buf.Reset()
buf.args = buf.args[:0]
bufFree.Put(buf)
}
// Value is an interface for value.
type Value interface {
String() string
Eval(w io.Writer, ev *Evaluator) error
serialize() serializableVar
dump(d *dumpbuf)
}
// literal is literal value.
type literal string
func (s literal) String() string { return string(s) }
func (s literal) Eval(w io.Writer, ev *Evaluator) error {
io.WriteString(w, string(s))
return nil
}
func (s literal) serialize() serializableVar {
return serializableVar{Type: "literal", V: string(s)}
}
func (s literal) dump(d *dumpbuf) {
d.Byte(valueTypeLiteral)
d.Bytes([]byte(s))
}
// tmpval is temporary value.
type tmpval []byte
func (t tmpval) String() string { return string(t) }
func (t tmpval) Eval(w io.Writer, ev *Evaluator) error {
w.Write(t)
return nil
}
func (t tmpval) Value() []byte { return []byte(t) }
func (t tmpval) serialize() serializableVar {
return serializableVar{Type: "tmpval", V: string(t)}
}
func (t tmpval) dump(d *dumpbuf) {
d.Byte(valueTypeTmpval)
d.Bytes(t)
}
// expr is a list of values.
type expr []Value
func (e expr) String() string {
var s []string
for _, v := range e {
s = append(s, v.String())
}
return strings.Join(s, "")
}
func (e expr) Eval(w io.Writer, ev *Evaluator) error {
for _, v := range e {
err := v.Eval(w, ev)
if err != nil {
return err
}
}
return nil
}
func (e expr) serialize() serializableVar {
r := serializableVar{Type: "expr"}
for _, v := range e {
r.Children = append(r.Children, v.serialize())
}
return r
}
func (e expr) dump(d *dumpbuf) {
d.Byte(valueTypeExpr)
d.Int(len(e))
for _, v := range e {
v.dump(d)
}
}
func compactExpr(e expr) Value {
if len(e) == 1 {
return e[0]
}
// TODO(ukai): concat literal
return e
}
// varref is variable reference. e.g. ${foo}.
type varref struct {
varname Value
}
func (v *varref) String() string {
varname := v.varname.String()
if len(varname) == 1 {
return fmt.Sprintf("$%s", varname)
}
return fmt.Sprintf("${%s}", varname)
}
func (v *varref) Eval(w io.Writer, ev *Evaluator) error {
te := traceEvent.begin("var", v, traceEventMain)
buf := newBuf()
err := v.varname.Eval(buf, ev)
if err != nil {
return err
}
vv := ev.LookupVar(buf.String())
freeBuf(buf)
err = vv.Eval(w, ev)
if err != nil {
return err
}
traceEvent.end(te)
return nil
}
func (v *varref) serialize() serializableVar {
return serializableVar{
Type: "varref",
Children: []serializableVar{v.varname.serialize()},
}
}
func (v *varref) dump(d *dumpbuf) {
d.Byte(valueTypeVarref)
v.varname.dump(d)
}
// paramref is parameter reference e.g. $1.
type paramref int
func (p paramref) String() string {
return fmt.Sprintf("$%d", int(p))
}
func (p paramref) Eval(w io.Writer, ev *Evaluator) error {
te := traceEvent.begin("param", p, traceEventMain)
n := int(p)
if n < len(ev.paramVars) {
err := ev.paramVars[n].Eval(w, ev)
if err != nil {
return err
}
} else {
vv := ev.LookupVar(fmt.Sprintf("%d", n))
err := vv.Eval(w, ev)
if err != nil {
return err
}
}
traceEvent.end(te)
return nil
}
func (p paramref) serialize() serializableVar {
return serializableVar{Type: "paramref", V: strconv.Itoa(int(p))}
}
func (p paramref) dump(d *dumpbuf) {
d.Byte(valueTypeParamref)
d.Int(int(p))
}
// varsubst is variable substitutaion. e.g. ${var:pat=subst}.
type varsubst struct {
varname Value
pat Value
subst Value
}
func (v varsubst) String() string {
return fmt.Sprintf("${%s:%s=%s}", v.varname, v.pat, v.subst)
}
func (v varsubst) Eval(w io.Writer, ev *Evaluator) error {
te := traceEvent.begin("varsubst", v, traceEventMain)
buf := newBuf()
params, err := ev.args(buf, v.varname, v.pat, v.subst)
if err != nil {
return err
}
vname := string(params[0])
pat := string(params[1])
subst := string(params[2])
buf.Reset()
vv := ev.LookupVar(vname)
err = vv.Eval(buf, ev)
if err != nil {
return err
}
vals := splitSpaces(buf.String())
freeBuf(buf)
space := false
for _, val := range vals {
if space {
io.WriteString(w, " ")
}
io.WriteString(w, substRef(pat, subst, val))
space = true
}
traceEvent.end(te)
return nil
}
func (v varsubst) serialize() serializableVar {
return serializableVar{
Type: "varsubst",
Children: []serializableVar{
v.varname.serialize(),
v.pat.serialize(),
v.subst.serialize(),
},
}
}
func (v varsubst) dump(d *dumpbuf) {
d.Byte(valueTypeVarsubst)
v.varname.dump(d)
v.pat.dump(d)
v.subst.dump(d)
}
func str(buf []byte, alloc bool) Value {
if alloc {
return literal(string(buf))
}
return tmpval(buf)
}
func appendStr(exp expr, buf []byte, alloc bool) expr {
if len(buf) == 0 {
return exp
}
if len(exp) == 0 {
return append(exp, str(buf, alloc))
}
switch v := exp[len(exp)-1].(type) {
case literal:
v += literal(string(buf))
exp[len(exp)-1] = v
return exp
case tmpval:
v = append(v, buf...)
exp[len(exp)-1] = v
return exp
}
return append(exp, str(buf, alloc))
}
func valueNum(v Value) (int, error) {
switch v := v.(type) {
case literal, tmpval:
n, err := strconv.ParseInt(v.String(), 10, 64)
return int(n), err
}
return 0, errNotLiteral
}
// parseExpr parses expression in `in` until it finds any byte in term.
// if term is nil, it will parse to end of input.
// if term is not nil, and it reaches to end of input, return errEndOfInput.
// it returns parsed value, and parsed length `n`, so in[n-1] is any byte of
// term, and in[n:] is next input.
// if alloc is true, text will be literal (allocate string).
// otherwise, text will be tmpval on in.
func parseExpr(in, term []byte, alloc bool) (Value, int, error) {
var exp expr
b := 0
i := 0
var saveParen byte
parenDepth := 0
Loop:
for i < len(in) {
ch := in[i]
if term != nil && bytes.IndexByte(term, ch) >= 0 {
break Loop
}
switch ch {
case '$':
if i+1 >= len(in) {
break Loop
}
if in[i+1] == '$' {
exp = appendStr(exp, in[b:i+1], alloc)
i += 2
b = i
continue
}
if bytes.IndexByte(term, in[i+1]) >= 0 {
exp = appendStr(exp, in[b:i], alloc)
exp = append(exp, &varref{varname: literal("")})
i++
b = i
break Loop
}
exp = appendStr(exp, in[b:i], alloc)
v, n, err := parseDollar(in[i:], alloc)
if err != nil {
return nil, 0, err
}
i += n
b = i
exp = append(exp, v)
continue
case '(', '{':
cp := closeParen(ch)
if i := bytes.IndexByte(term, cp); i >= 0 {
parenDepth++
saveParen = cp
term[i] = 0
} else if cp == saveParen {
parenDepth++
}
case saveParen:
parenDepth--
if parenDepth == 0 {
i := bytes.IndexByte(term, 0)
term[i] = saveParen
saveParen = 0
}
}
i++
}
exp = appendStr(exp, in[b:i], alloc)
if i == len(in) && term != nil {
return exp, i, errEndOfInput
}
return compactExpr(exp), i, nil
}
func closeParen(ch byte) byte {
switch ch {
case '(':
return ')'
case '{':
return '}'
}
return 0
}
// parseDollar parses
// $(func expr[, expr...]) # func = literal SP
// $(expr:expr=expr)
// $(expr)
// $x
// it returns parsed value and parsed length.
func parseDollar(in []byte, alloc bool) (Value, int, error) {
if len(in) <= 1 {
return nil, 0, errors.New("empty expr")
}
if in[0] != '$' {
return nil, 0, errors.New("should starts with $")
}
if in[1] == '$' {
return nil, 0, errors.New("should handle $$ as literal $")
}
paren := closeParen(in[1])
if paren == 0 {
// $x case.
if in[1] >= '0' && in[1] <= '9' {
return paramref(in[1] - '0'), 2, nil
}
return &varref{varname: str(in[1:2], alloc)}, 2, nil
}
term := []byte{paren, ':', ' '}
var varname expr
i := 2
Again:
for {
e, n, err := parseExpr(in[i:], term, alloc)
if err != nil {
return nil, 0, err
}
varname = append(varname, e)
i += n
switch in[i] {
case paren:
// ${expr}
vname := compactExpr(varname)
n, err := valueNum(vname)
if err == nil {
// ${n}
return paramref(n), i + 1, nil
}
return &varref{varname: vname}, i + 1, nil
case ' ':
// ${e ...}
switch token := e.(type) {
case literal, tmpval:
funcName := intern(token.String())
if f, ok := funcMap[funcName]; ok {
return parseFunc(f(), in, i+1, term[:1], funcName, alloc)
}
}
term = term[:2] // drop ' '
continue Again
case ':':
// ${varname:...}
colon := in[i : i+1]
term = term[:2]
term[1] = '=' // term={paren, '='}.
e, n, err := parseExpr(in[i+1:], term, alloc)
if err != nil {
return nil, 0, err
}
i += 1 + n
if in[i] == paren {
varname = appendStr(varname, colon, alloc)
return &varref{varname: varname}, i + 1, nil
}
// ${varname:xx=...}
pat := e
subst, n, err := parseExpr(in[i+1:], term[:1], alloc)
if err != nil {
return nil, 0, err
}
i += 1 + n
// ${first:pat=e}
return varsubst{
varname: compactExpr(varname),
pat: pat,
subst: subst,
}, i + 1, nil
default:
return nil, 0, fmt.Errorf("unexpected char %c at %d in %q", in[i], i, string(in))
}
}
}
// skipSpaces skips spaces at front of `in` before any bytes in term.
// in[n] will be the first non white space in in.
func skipSpaces(in, term []byte) int {
for i := 0; i < len(in); i++ {
if bytes.IndexByte(term, in[i]) >= 0 {
return i
}
switch in[i] {
case ' ', '\t':
default:
return i
}
}
return len(in)
}
// trimLiteralSpace trims literal space around v.
func trimLiteralSpace(v Value) Value {
switch v := v.(type) {
case literal:
return literal(strings.TrimSpace(string(v)))
case tmpval:
b := bytes.TrimSpace([]byte(v))
if len(b) == 0 {
return literal("")
}
return tmpval(b)
case expr:
if len(v) == 0 {
return v
}
switch s := v[0].(type) {
case literal, tmpval:
t := trimLiteralSpace(s)
if t == literal("") {
v = v[1:]
} else {
v[0] = t
}
}
switch s := v[len(v)-1].(type) {
case literal, tmpval:
t := trimLiteralSpace(s)
if t == literal("") {
v = v[:len(v)-1]
} else {
v[len(v)-1] = t
}
}
return compactExpr(v)
}
return v
}
// concatLine concatinates line with "\\\n" in function expression.
// TODO(ukai): less alloc?
func concatLine(v Value) Value {
switch v := v.(type) {
case literal:
for {
s := string(v)
i := strings.Index(s, "\\\n")
if i < 0 {
return v
}
v = literal(s[:i] + strings.TrimLeft(s[i+2:], " \t"))
}
case tmpval:
for {
b := []byte(v)
i := bytes.Index(b, []byte{'\\', '\n'})
if i < 0 {
return v
}
var buf bytes.Buffer
buf.Write(b[:i])
buf.Write(bytes.TrimLeft(b[i+2:], " \t"))
v = tmpval(buf.Bytes())
}
case expr:
for i := range v {
switch vv := v[i].(type) {
case literal, tmpval:
v[i] = concatLine(vv)
}
}
return v
}
return v
}
// parseFunc parses function arguments from in[s:] for f.
// in[0] is '$' and in[s] is space just after func name.
// in[:n] will be "${func args...}"
func parseFunc(f mkFunc, in []byte, s int, term []byte, funcName string, alloc bool) (Value, int, error) {
f.AddArg(str(in[1:s-1], alloc))
arity := f.Arity()
term = append(term, ',')
i := skipSpaces(in[s:], term)
i = s + i
if i == len(in) {
return f, i, nil
}
narg := 1
for {
if arity != 0 && narg >= arity {
// final arguments.
term = term[:1] // drop ','
}
v, n, err := parseExpr(in[i:], term, alloc)
if err != nil {
return nil, 0, err
}
v = concatLine(v)
// TODO(ukai): do this in funcIf, funcAnd, or funcOr's compactor?
if (narg == 1 && funcName == "if") || funcName == "and" || funcName == "or" {
v = trimLiteralSpace(v)
}
f.AddArg(v)
i += n
narg++
if in[i] == term[0] {
i++
break
}
i++ // should be ','
if i == len(in) {
break
}
}
var fv Value
fv = f
if compactor, ok := f.(compactor); ok {
fv = compactor.Compact()
}
if EvalStatsFlag || traceEvent.enabled() {
fv = funcstats{
Value: fv,
str: fv.String(),
}
}
return fv, i, nil
}
type compactor interface {
Compact() Value
}
type funcstats struct {
Value
str string
}
func (f funcstats) Eval(w io.Writer, ev *Evaluator) error {
te := traceEvent.begin("func", literal(f.str), traceEventMain)
err := f.Value.Eval(w, ev)
if err != nil {
return err
}
// TODO(ukai): per functype?
traceEvent.end(te)
return nil
}
type matcherValue struct{}
func (m matcherValue) Eval(w io.Writer, ev *Evaluator) error {
return fmt.Errorf("couldn't eval matcher")
}
func (m matcherValue) serialize() serializableVar {
return serializableVar{Type: ""}
}
func (m matcherValue) dump(d *dumpbuf) {
d.err = fmt.Errorf("couldn't dump matcher")
}
type matchVarref struct{ matcherValue }
func (m matchVarref) String() string { return "$(match-any)" }
type literalRE struct {
matcherValue
*regexp.Regexp
}
func mustLiteralRE(s string) literalRE {
return literalRE{
Regexp: regexp.MustCompile(s),
}
}
func (r literalRE) String() string { return r.Regexp.String() }
func matchValue(exp, pat Value) bool {
switch pat := pat.(type) {
case literal:
return literal(exp.String()) == pat
}
// TODO: other type match?
return false
}
func matchExpr(exp, pat expr) ([]Value, bool) {
if len(exp) != len(pat) {
return nil, false
}
var mv matchVarref
var matches []Value
for i := range exp {
if pat[i] == mv {
switch exp[i].(type) {
case paramref, *varref:
matches = append(matches, exp[i])
continue
}
return nil, false
}
if patre, ok := pat[i].(literalRE); ok {
re := patre.Regexp
m := re.FindStringSubmatch(exp[i].String())
if m == nil {
return nil, false
}
for _, sm := range m[1:] {
matches = append(matches, literal(sm))
}
continue
}
if !matchValue(exp[i], pat[i]) {
return nil, false
}
}
return matches, true
}