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