Rewrite file name functions
diff --git a/func.go b/func.go
index b359e26..a840e44 100644
--- a/func.go
+++ b/func.go
@@ -6,7 +6,6 @@
 	"io"
 	"os"
 	"os/exec"
-	"path/filepath"
 	"sort"
 	"strconv"
 	"strings"
@@ -67,6 +66,17 @@
 		"firstword":  func() Func { return &funcFirstword{} },
 		"lastword":   func() Func { return &funcLastword{} },
 
+		"join":      func() Func { return &funcJoin{} },
+		"wildcard":  func() Func { return &funcWildcard{} },
+		"dir":       func() Func { return &funcDir{} },
+		"notdir":    func() Func { return &funcNotdir{} },
+		"suffix":    func() Func { return &funcSuffix{} },
+		"basename":  func() Func { return &funcBasename{} },
+		"addsuffix": func() Func { return &funcAddsuffix{} },
+		"addprefix": func() Func { return &funcAddprefix{} },
+		"realpath":  func() Func { return &funcRealpath{} },
+		"abspath":   func() Func { return &funcAbspath{} },
+
 		"shell":   func() Func { return &funcShell{} },
 		"call":    func() Func { return &funcCall{} },
 		"foreach": func() Func { return &funcForeach{} },
@@ -75,26 +85,26 @@
 
 func init() {
 	/*
-		fwrap("findstring", 2, funcFindstring)
-		fwrap("filter", 2, funcFilter)
-		fwrap("filter-out", 2, funcFilterOut)
-		fwrap("sort", 1, funcSort)
-		fwrap("word", 2, funcWord)
-		fwrap("wordlist", 3, funcWordlist)
-		fwrap("words", 1, funcWords)
-		fwrap("firstword", 1, funcFirstword)
-		fwrap("lastword", 1, funcLastword)
+			fwrap("findstring", 2, funcFindstring)
+			fwrap("filter", 2, funcFilter)
+			fwrap("filter-out", 2, funcFilterOut)
+			fwrap("sort", 1, funcSort)
+			fwrap("word", 2, funcWord)
+			fwrap("wordlist", 3, funcWordlist)
+			fwrap("words", 1, funcWords)
+			fwrap("firstword", 1, funcFirstword)
+			fwrap("lastword", 1, funcLastword)
+		fwrap("join", 2, funcJoin)
+		fwrap("wildcard", 1, funcWildcard)
+		fwrap("dir", 1, funcDir)
+		fwrap("notdir", 1, funcNotdir)
+		fwrap("suffix", 1, funcSuffix)
+		fwrap("basename", 1, funcBasename)
+		fwrap("addsuffix", 2, funcAddsuffix)
+		fwrap("addprefix", 2, funcAddprefix)
+		fwrap("realpath", 1, funcRealpath)
+		fwrap("abspath", 1, funcAbspath)
 	*/
-	fwrap("join", 2, funcJoin)
-	fwrap("wildcard", 1, funcWildcard)
-	fwrap("dir", 1, funcDir)
-	fwrap("notdir", 1, funcNotdir)
-	fwrap("suffix", 1, funcSuffix)
-	fwrap("basename", 1, funcBasename)
-	fwrap("addsuffix", 2, funcAddsuffix)
-	fwrap("addprefix", 2, funcAddprefix)
-	fwrap("realpath", 1, funcRealpath)
-	fwrap("abspath", 1, funcAbspath)
 	fwrap("if", 3, funcIf)
 	fwrap("and", 0, funcAnd)
 	fwrap("or", 0, funcOr)
@@ -330,6 +340,95 @@
 	w.Write(toks[len(toks)-1])
 }
 
+// https://www.gnu.org/software/make/manual/html_node/File-Name-Functions.html#File-Name-Functions
+
+type funcJoin struct{ fclosure }
+
+func (f *funcJoin) Arity() int { return 2 }
+func (f *funcJoin) Eval(w io.Writer, ev *Evaluator) {
+	assertArity("join", 2, len(f.args))
+	list1 := splitSpacesBytes(ev.Value(f.args[0]))
+	list2 := splitSpacesBytes(ev.Value(f.args[1]))
+	sw := ssvWriter{w: w}
+	for i, v := range list1 {
+		if i < len(list2) {
+			sw.Write(v)
+			// Use |w| not to append extra ' '.
+			w.Write(list2[i])
+			continue
+		}
+		sw.Write(v)
+	}
+	if len(list2) > len(list1) {
+		for _, v := range list2[len(list1):] {
+			sw.Write(v)
+		}
+	}
+}
+
+type funcWildcard struct{ fclosure }
+
+func (f *funcWildcard) Arity() int { return 1 }
+func (f *funcWildcard) Eval(w io.Writer, ev *Evaluator) {
+	assertArity("wildcard", 1, len(f.args))
+}
+
+type funcDir struct{ fclosure }
+
+func (f *funcDir) Arity() int { return 1 }
+func (f *funcDir) Eval(w io.Writer, ev *Evaluator) {
+	assertArity("dir", 1, len(f.args))
+}
+
+type funcNotdir struct{ fclosure }
+
+func (f *funcNotdir) Arity() int { return 1 }
+func (f *funcNotdir) Eval(w io.Writer, ev *Evaluator) {
+	assertArity("notdir", 1, len(f.args))
+}
+
+type funcSuffix struct{ fclosure }
+
+func (f *funcSuffix) Arity() int { return 1 }
+func (f *funcSuffix) Eval(w io.Writer, ev *Evaluator) {
+	assertArity("suffix", 1, len(f.args))
+}
+
+type funcBasename struct{ fclosure }
+
+func (f *funcBasename) Arity() int { return 1 }
+func (f *funcBasename) Eval(w io.Writer, ev *Evaluator) {
+	assertArity("basename", 1, len(f.args))
+}
+
+type funcAddsuffix struct{ fclosure }
+
+func (f *funcAddsuffix) Arity() int { return 2 }
+func (f *funcAddsuffix) Eval(w io.Writer, ev *Evaluator) {
+	assertArity("addsuffix", 2, len(f.args))
+}
+
+type funcAddprefix struct{ fclosure }
+
+func (f *funcAddprefix) Arity() int { return 2 }
+func (f *funcAddprefix) Eval(w io.Writer, ev *Evaluator) {
+	assertArity("addprefix", 2, len(f.args))
+}
+
+type funcRealpath struct{ fclosure }
+
+func (f *funcRealpath) Arity() int { return 1 }
+func (f *funcRealpath) Eval(w io.Writer, ev *Evaluator) {
+	assertArity("realpath", 1, len(f.args))
+}
+
+type funcAbspath struct{ fclosure }
+
+func (f *funcAbspath) Arity() int { return 1 }
+func (f *funcAbspath) Eval(w io.Writer, ev *Evaluator) {
+	assertArity("abspath", 1, len(f.args))
+}
+
 // http://www.gnu.org/software/make/manual/make.html#Shell-Function
 type funcShell struct{ fclosure }
 
@@ -464,150 +563,6 @@
 	return args
 }
 
-// http://www.gnu.org/software/make/manual/make.html#File-Name-Functions
-func funcJoin(ev *Evaluator, args []string) string {
-	args = arity("join", 2, args)
-	list1 := splitSpaces(ev.evalExpr(args[0]))
-	list2 := splitSpaces(ev.evalExpr(args[1]))
-	var results []string
-	for i, v := range list1 {
-		if i < len(list2) {
-			results = append(results, v+list2[i])
-			continue
-		}
-		results = append(results, v)
-	}
-	if len(list2) > len(list1) {
-		for _, v := range list2[len(list1):] {
-			results = append(results, v)
-		}
-	}
-	return strings.Join(results, " ")
-}
-
-func funcWildcard(ev *Evaluator, args []string) string {
-	args = arity("wildcard", 1, args)
-	var result []string
-	for _, pattern := range splitSpaces(ev.evalExpr(args[0])) {
-		files, err := filepath.Glob(pattern)
-		if err != nil {
-			panic(err)
-		}
-		result = append(result, files...)
-	}
-	return strings.Join(result, " ")
-}
-
-// https://www.gnu.org/software/make/manual/html_node/File-Name-Functions.html#File-Name-Functions
-func funcDir(ev *Evaluator, args []string) string {
-	args = arity("dir", 1, args)
-	names := splitSpaces(ev.evalExpr(args[0]))
-	if len(names) == 0 {
-		return ""
-	}
-	var dirs []string
-	for _, name := range names {
-		dirs = append(dirs, filepath.Dir(name)+string(filepath.Separator))
-	}
-	return strings.Join(dirs, " ")
-}
-
-func funcNotdir(ev *Evaluator, args []string) string {
-	args = arity("notdir", 1, args)
-	names := splitSpaces(ev.evalExpr(args[0]))
-	if len(names) == 0 {
-		return ""
-	}
-	var notdirs []string
-	for _, name := range names {
-		if name == string(filepath.Separator) {
-			notdirs = append(notdirs, "")
-			continue
-		}
-		notdirs = append(notdirs, filepath.Base(name))
-	}
-	return strings.Join(notdirs, " ")
-}
-
-func funcSuffix(ev *Evaluator, args []string) string {
-	args = arity("suffix", 1, args)
-	toks := splitSpaces(ev.evalExpr(args[0]))
-	var result []string
-	for _, tok := range toks {
-		e := filepath.Ext(tok)
-		if len(e) > 0 {
-			result = append(result, e)
-		}
-	}
-	return strings.Join(result, " ")
-}
-
-func funcBasename(ev *Evaluator, args []string) string {
-	args = arity("basename", 1, args)
-	toks := splitSpaces(ev.evalExpr(args[0]))
-	var result []string
-	for _, tok := range toks {
-		b := stripExt(tok)
-		result = append(result, b)
-	}
-	return strings.Join(result, " ")
-}
-
-func funcAddsuffix(ev *Evaluator, args []string) string {
-	args = arity("addsuffix", 2, args)
-	suf := ev.evalExpr(args[0])
-	toks := splitSpaces(ev.evalExpr(args[1]))
-	for i, tok := range toks {
-		toks[i] = fmt.Sprintf("%s%s", tok, suf)
-	}
-	return strings.Join(toks, " ")
-}
-
-func funcAddprefix(ev *Evaluator, args []string) string {
-	args = arity("addprefix", 2, args)
-	pre := ev.evalExpr(args[0])
-	toks := splitSpaces(ev.evalExpr(args[1]))
-	for i, tok := range toks {
-		toks[i] = fmt.Sprintf("%s%s", pre, tok)
-	}
-	return strings.Join(toks, " ")
-}
-
-func funcRealpath(ev *Evaluator, args []string) string {
-	args = arity("realpath", 1, args)
-	names := splitSpaces(ev.evalExpr(args[0]))
-	var realpaths []string
-	for _, name := range names {
-		name, err := filepath.Abs(name)
-		if err != nil {
-			Log("abs: %v", err)
-			continue
-		}
-		name, err = filepath.EvalSymlinks(name)
-		if err != nil {
-			Log("realpath: %v", err)
-			continue
-		}
-		realpaths = append(realpaths, name)
-	}
-	return strings.Join(realpaths, " ")
-}
-
-func funcAbspath(ev *Evaluator, args []string) string {
-	args = arity("abspath", 1, args)
-	names := splitSpaces(ev.evalExpr(args[0]))
-	var realpaths []string
-	for _, name := range names {
-		name, err := filepath.Abs(name)
-		if err != nil {
-			Log("abs: %v", err)
-			continue
-		}
-		realpaths = append(realpaths, name)
-	}
-	return strings.Join(realpaths, " ")
-}
-
 // http://www.gnu.org/software/make/manual/make.html#Conditional-Functions
 func funcIf(ev *Evaluator, args []string) string {
 	if len(args) < 2 {