factor out func.go
diff --git a/eval.go b/eval.go
index baa36cf..040995a 100644
--- a/eval.go
+++ b/eval.go
@@ -3,9 +3,6 @@
 import (
 	"bytes"
 	"fmt"
-	"os/exec"
-	"path/filepath"
-	"regexp"
 	"strings"
 )
 
@@ -28,6 +25,8 @@
 	vars     map[string]string
 	curRule  *Rule
 
+	funcs map[string]Func
+
 	filename string
 	lineno   int
 }
@@ -37,39 +36,23 @@
 		outVars: make(map[string]string),
 		refs:    make(map[string]bool),
 		vars:    make(map[string]string),
+		funcs: map[string]Func{
+			"wildcard": funcWildcard,
+			"shell":    funcShell,
+			"warning":  funcWarning,
+		},
 	}
 }
 
 func (ev *Evaluator) evalFunction(ex string) (string, bool) {
-	if strings.HasPrefix(ex, "wildcard ") {
-		arg := ex[len("wildcard "):]
-
-		files, err := filepath.Glob(arg)
-		if err != nil {
-			panic(err)
-		}
-		return strings.Join(files, " "), true
-	} else if strings.HasPrefix(ex, "shell ") {
-		arg := ex[len("shell "):]
-
-		args := []string{"/bin/sh", "-c", arg}
-		cmd := exec.Cmd{
-			Path: args[0],
-			Args: args,
-		}
-		out, err := cmd.CombinedOutput()
-		if err != nil {
-			panic(err)
-		}
-		re, err := regexp.Compile(`\s`)
-		if err != nil {
-			panic(err)
-		}
-		return string(re.ReplaceAllString(string(out), " ")), true
-	} else if strings.HasPrefix(ex, "warning ") {
-		arg := ex[len("warning "):]
-		fmt.Printf("%s:%d: %s\n", ev.filename, ev.lineno, arg)
-		return "", true
+	i := strings.IndexAny(ex, " \t")
+	if i < 0 {
+		return "", false
+	}
+	cmd := strings.TrimSpace(ex[:i])
+	args := strings.TrimLeft(ex[i+1:], " \t")
+	if f, ok := ev.funcs[cmd]; ok {
+		return f(ev, args), true
 	}
 	return "", false
 }
diff --git a/func.go b/func.go
new file mode 100644
index 0000000..0d05664
--- /dev/null
+++ b/func.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+	"fmt"
+	"os/exec"
+	"path/filepath"
+	"regexp"
+	"strings"
+)
+
+// 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
+
+// http://www.gnu.org/software/make/manual/make.html#File-Name-Functions
+func funcWildcard(_ *Evaluator, arg string) string {
+	files, err := filepath.Glob(arg)
+	if err != nil {
+		panic(err)
+	}
+	return strings.Join(files, " ")
+}
+
+// http://www.gnu.org/software/make/manual/make.html#Shell-Function
+func funcShell(_ *Evaluator, arg string) string {
+	args := []string{"/bin/sh", "-c", arg}
+	cmd := exec.Cmd{
+		Path: args[0],
+		Args: args,
+	}
+	out, err := cmd.CombinedOutput()
+	if err != nil {
+		panic(err)
+	}
+	re, err := regexp.Compile(`\s`)
+	if err != nil {
+		panic(err)
+	}
+	return string(re.ReplaceAllString(string(out), " "))
+}
+
+// http://www.gnu.org/software/make/manual/make.html#Make-Control-Functions
+func funcWarning(ev *Evaluator, arg string) string {
+	fmt.Printf("%s:%d: %s\n", ev.filename, ev.lineno, arg)
+	return ""
+}