fix subst.mk
add wildcard_with_commas.mk
add go_test make targets for run go unittest.
diff --git a/Makefile b/Makefile
index 17f66bc..39ddcaa 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,13 @@
GOSRC = $(wildcard *.go)
-all: kati
+all: kati go_test
kati: $(GOSRC)
env $(shell go env) go build -o $@ .
+go_test: $(GOSRC)
+ env $(shell go env) go test .
+
test: all
ruby runtest.rb
diff --git a/eval.go b/eval.go
index 0925765..e0e1dd4 100644
--- a/eval.go
+++ b/eval.go
@@ -41,6 +41,7 @@
refs: make(map[string]bool),
vars: vars,
funcs: map[string]Func{
+ "subst": funcSubst,
"wildcard": funcWildcard,
"shell": funcShell,
"warning": funcWarning,
@@ -48,29 +49,33 @@
}
}
-func (ev *Evaluator) evalFunction(ex string) (string, bool) {
- i := strings.IndexAny(ex, " \t")
+func (ev *Evaluator) evalFunction(args []string) (string, bool) {
+ if len(args) == 0 {
+ return "", false
+ }
+ i := strings.IndexAny(args[0], " \t")
if i < 0 {
return "", false
}
- cmd := strings.TrimSpace(ex[:i])
- args := strings.TrimLeft(ex[i+1:], " \t")
+ cmd := strings.TrimSpace(args[0][:i])
+ args[0] = strings.TrimLeft(args[0][i+1:], " \t")
if f, ok := ev.funcs[cmd]; ok {
return f(ev, args), true
}
return "", false
}
-func (ev *Evaluator) evalExprSlice(ex string, term byte) (string, int) {
+func (ev *Evaluator) evalExprSlice(ex string) (string, int) {
var buf bytes.Buffer
i := 0
- for i < len(ex) && ex[i] != term {
+Loop:
+ for i < len(ex) {
ch := ex[i]
i++
switch ch {
case '$':
- if i >= len(ex) || ex[i] == term {
- continue
+ if i >= len(ex) {
+ break Loop
}
var varname string
@@ -80,18 +85,16 @@
i++
continue
case '(', '{':
- var cp byte = ')'
- if ex[i] == '{' {
- cp = '}'
+ args, rest, err := parseExpr(ex[i:])
+ if err != nil {
}
- v, j := ev.evalExprSlice(ex[i+1:], cp)
- i += j + 2
- if r, done := ev.evalFunction(v); done {
+ i += rest
+ if r, done := ev.evalFunction(args); done {
buf.WriteString(r)
continue
}
- varname = v
+ varname = strings.Join(args, ",")
default:
varname = string(ex[i])
i++
@@ -112,7 +115,7 @@
}
func (ev *Evaluator) evalExpr(ex string) string {
- r, i := ev.evalExprSlice(ex, 0)
+ r, i := ev.evalExprSlice(ex)
if len(ex) != i {
panic(fmt.Sprintf("Had a null character? %q, %d", ex, i))
}
diff --git a/func.go b/func.go
index 0b23e76..5d527ca 100644
--- a/func.go
+++ b/func.go
@@ -10,14 +10,25 @@
// Func is a make function.
// http://www.gnu.org/software/make/manual/make.html#Functions
-// TODO(ukai): *Evaluator -> eval context or so?
// TODO(ukai): return error instead of panic?
-type Func func(*Evaluator, string) string
+type Func func(*Evaluator, []string) string
+
+func funcSubst(ev *Evaluator, args []string) string {
+ Log("subst %q", args)
+ if len(args) != 3 {
+ panic(fmt.Sprintf("*** insufficient number of arguments (%d) to function `subst'."))
+ }
+ from := ev.evalExpr(args[0])
+ to := ev.evalExpr(args[1])
+ text := ev.evalExpr(args[2])
+ return strings.Replace(text, from, to, -1)
+}
// http://www.gnu.org/software/make/manual/make.html#File-Name-Functions
-func funcWildcard(_ *Evaluator, arg string) string {
- Log("wildcard %q", arg)
- files, err := filepath.Glob(arg)
+func funcWildcard(ev *Evaluator, args []string) string {
+ Log("wildcard %q", args)
+ pattern := ev.evalExpr(strings.Join(args, ","))
+ files, err := filepath.Glob(pattern)
if err != nil {
panic(err)
}
@@ -25,12 +36,13 @@
}
// http://www.gnu.org/software/make/manual/make.html#Shell-Function
-func funcShell(_ *Evaluator, arg string) string {
- Log("shell %q", arg)
- args := []string{"/bin/sh", "-c", arg}
+func funcShell(ev *Evaluator, args []string) string {
+ Log("shell %q", args)
+ arg := ev.evalExpr(strings.Join(args, ","))
+ cmdline := []string{"/bin/sh", "-c", arg}
cmd := exec.Cmd{
- Path: args[0],
- Args: args,
+ Path: cmdline[0],
+ Args: cmdline,
}
out, err := cmd.CombinedOutput()
if err != nil {
@@ -44,8 +56,9 @@
}
// http://www.gnu.org/software/make/manual/make.html#Make-Control-Functions
-func funcWarning(ev *Evaluator, arg string) string {
- Log("warning %q", arg)
+func funcWarning(ev *Evaluator, args []string) string {
+ Log("warning %q", args)
+ arg := ev.evalExpr(strings.Join(args, ","))
fmt.Printf("%s:%d: %s\n", ev.filename, ev.lineno, arg)
return ""
}
diff --git a/parser.go b/parser.go
index 0815e6b..133d100 100644
--- a/parser.go
+++ b/parser.go
@@ -157,52 +157,76 @@
return ast
}
-func (p *parser) parseEq(s string) (string, string, bool) {
- if len(s) == 0 || s[0] != '(' {
- return "", "", false
+func closeParen(ch byte) (byte, error) {
+ switch ch {
+ case '(':
+ return ')', nil
+ case '{':
+ return '}', nil
+ default:
+ return 0, fmt.Errorf("unexpected paren %c", ch)
}
+}
+// parseExpr parses s as expr.
+// The expr should starts with '(' or '{' and returns strings
+// separeted by ',' before ')' or '}' respectively, and an index for the rest.
+func parseExpr(s string) ([]string, int, error) {
+ if len(s) == 0 {
+ return nil, 0, errors.New("empty expr")
+ }
+ paren, err := closeParen(s[0])
+ if err != nil {
+ return nil, 0, err
+ }
+ parenCnt := make(map[byte]int)
i := 0
- parenCnt := 0
- inRhs := false
- var lhs []byte
- var rhs []byte
+ ia := 1
+ var args []string
+Loop:
for {
i++
if i == len(s) {
- return "", "", false
+ return nil, 0, errors.New("unexpected end of expr")
}
ch := s[i]
- if ch == '(' {
- parenCnt++
- } else if ch == ')' {
- parenCnt--
- if parenCnt < 0 {
- if inRhs {
- break
- } else {
- return "", "", false
- }
+ switch ch {
+ case '(', '{':
+ cch, err := closeParen(ch)
+ if err != nil {
+ return nil, 0, err
}
- } else if ch == ',' {
- if inRhs {
- return "", "", false
- } else {
- inRhs = true
- continue
+ parenCnt[cch]++
+ case ')', '}':
+ parenCnt[ch]--
+ if ch == paren && parenCnt[ch] < 0 {
+ break Loop
}
- }
- if inRhs {
- rhs = append(rhs, ch)
- } else {
- lhs = append(lhs, ch)
+ case ',':
+ if parenCnt[')'] == 0 && parenCnt['}'] == 0 {
+ args = append(args, s[ia:i])
+ ia = i + 1
+ }
}
}
- return string(lhs), string(rhs), true
+ args = append(args, s[ia:i])
+ return args, i + 1, nil
+}
+
+func parseEq(s string) (string, string, bool) {
+ args, _, err := parseExpr(s)
+ if err != nil {
+ return "", "", false
+ }
+ if len(args) != 2 {
+ return "", "", false
+ }
+ // TODO: check rest?
+ return args[0], args[1], true
}
func (p *parser) parseIfeq(line string, oplen int) AST {
- lhs, rhs, ok := p.parseEq(strings.TrimSpace(line[oplen+1:]))
+ lhs, rhs, ok := parseEq(strings.TrimSpace(line[oplen+1:]))
if !ok {
Error(p.filename, p.lineno, `*** invalid syntax in conditional.`)
}
diff --git a/parser_test.go b/parser_test.go
new file mode 100644
index 0000000..140ae1e
--- /dev/null
+++ b/parser_test.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestParseExpr(t *testing.T) {
+ for _, tc := range []struct {
+ in string
+ args []string
+ rest string
+ isErr bool
+ }{
+ {
+ in: "foo",
+ isErr: true,
+ },
+ {
+ in: "(foo)",
+ args: []string{"foo"},
+ },
+ {
+ in: "{foo}",
+ args: []string{"foo"},
+ },
+ {
+ in: "(lhs,rhs)",
+ args: []string{"lhs", "rhs"},
+ },
+ {
+ in: "(subst $(space),$(,),$(foo))/bar",
+ args: []string{"subst $(space)", "$(,)", "$(foo)"},
+ rest: "/bar",
+ },
+ } {
+ args, rest, err := parseExpr(tc.in)
+ if tc.isErr {
+ if err == nil {
+ t.Errorf(`parseExpr(%q)=_, _, nil; want error`, tc.in)
+ }
+ continue
+ }
+ if err != nil {
+ t.Errorf(`parseExpr(%q)=_, _, %v; want nil error`, tc.in, err)
+ continue
+ }
+ if got, want := args, tc.args; !reflect.DeepEqual(got, want) {
+ t.Errorf(`parseExpr(%q)=%q, _, _; want %q, _, _`, tc.in, got, want)
+ }
+ if got, want := tc.in[rest:], tc.rest; got != want {
+ t.Errorf(`parseExpr(%q)=_, %q, _; want _, %q, _`, tc.in, got, want)
+ }
+ }
+}
diff --git a/test/subst.mk b/test/subst.mk
index ef55532..a20e447 100644
--- a/test/subst.mk
+++ b/test/subst.mk
@@ -1,5 +1,3 @@
-# TODO
-
# http://www.gnu.org/software/make/manual/make.html#Syntax-of-Functions
comma:= ,
empty:=
diff --git a/test/wildcard_with_commas.mk b/test/wildcard_with_commas.mk
new file mode 100644
index 0000000..49bb2da
--- /dev/null
+++ b/test/wildcard_with_commas.mk
@@ -0,0 +1,10 @@
+files = $(wildcard *,*)
+
+test:
+ echo $(files)
+
+test2: foo,bar
+ echo $(files)
+
+foo,bar:
+ touch foo,bar