Implement word, wordlist, words, firstword, and lastword
diff --git a/eval.go b/eval.go
index 3429176..a6a2baa 100644
--- a/eval.go
+++ b/eval.go
@@ -33,6 +33,11 @@
 			"patsubst":   funcPatsubst,
 			"strip":      funcStrip,
 			"findstring": funcFindstring,
+			"word":       funcWord,
+			"wordlist":   funcWordlist,
+			"words":      funcWords,
+			"firstword":  funcFirstword,
+			"lastword":   funcLastword,
 			"wildcard":   funcWildcard,
 			"dir":        funcDir,
 			"notdir":     funcNotdir,
diff --git a/exec.go b/exec.go
index 19359d4..1198e2e 100644
--- a/exec.go
+++ b/exec.go
@@ -207,6 +207,8 @@
 		})
 	}
 	ev := newEvaluator(localVars)
+	ev.filename = rule.filename
+	ev.lineno = rule.cmdLineno
 	var cmds []string
 	for _, cmd := range rule.cmds {
 		if strings.IndexByte(cmd, '$') < 0 {
diff --git a/func.go b/func.go
index 7ada6ba..49b17b6 100644
--- a/func.go
+++ b/func.go
@@ -4,6 +4,7 @@
 	"fmt"
 	"os/exec"
 	"path/filepath"
+	"strconv"
 	"strings"
 )
 
@@ -60,11 +61,84 @@
 // filter
 // filter-out
 // sort
-// word
-// wordlist
-// words
-// firstword
-// lastword
+
+func numericValueForFunc(ev *Evaluator, a string, funcName string, nth string) int {
+	a = strings.TrimSpace(ev.evalExpr(a))
+	n, err := strconv.Atoi(a)
+	if err != nil || n < 0 {
+		Error(ev.filename, ev.lineno, `*** non-numeric %s argument to "%s" function: "%s".`, nth, funcName, a)
+	}
+	return n
+}
+
+func funcWord(ev *Evaluator, args []string) string {
+	if len(args) < 2 {
+		panic(fmt.Sprintf("*** insufficient number of arguments (%d) to function `word'.", len(args)))
+	}
+	index := numericValueForFunc(ev, args[0], "word", "first")
+	if index == 0 {
+		Error(ev.filename, ev.lineno, `*** first argument to "word" function must be greater than 0.`)
+	}
+	toks := splitSpaces(ev.evalExpr(strings.Join(args[1:], ",")))
+	if index-1 >= len(toks) {
+		return ""
+	}
+	return ev.evalExpr(toks[index-1])
+}
+
+func funcWordlist(ev *Evaluator, args []string) string {
+	if len(args) < 3 {
+		panic(fmt.Sprintf("*** insufficient number of arguments (%d) to function `wordlist'.", len(args)))
+	}
+	si := numericValueForFunc(ev, args[0], "wordlist", "first")
+	if si == 0 {
+		Error(ev.filename, ev.lineno, `*** invalid first argument to "wordlist" function: ""`, args[0])
+	}
+	ei := numericValueForFunc(ev, args[1], "wordlist", "second")
+	if ei == 0 {
+		Error(ev.filename, ev.lineno, `*** invalid second argument to "wordlist" function: ""`, args[1])
+	}
+
+	toks := splitSpaces(ev.evalExpr(strings.Join(args[2:], ",")))
+	if si-1 >= len(toks) {
+		return ""
+	}
+	if ei-1 >= len(toks) {
+		ei = len(toks)
+	}
+
+	return strings.Join(toks[si-1:ei], " ")
+}
+
+func funcWords(ev *Evaluator, args []string) string {
+	if len(args) <= 0 {
+		panic(fmt.Sprintf("*** insufficient number of arguments (%d) to function `words'.", len(args)))
+	}
+	toks := splitSpaces(ev.evalExpr(strings.Join(args, ",")))
+	return strconv.Itoa(len(toks))
+}
+
+func funcFirstword(ev *Evaluator, args []string) string {
+	if len(args) <= 0 {
+		panic(fmt.Sprintf("*** insufficient number of arguments (%d) to function `firstword'.", len(args)))
+	}
+	toks := splitSpaces(ev.evalExpr(strings.Join(args, ",")))
+	if len(toks) == 0 {
+		return ""
+	}
+	return toks[0]
+}
+
+func funcLastword(ev *Evaluator, args []string) string {
+	if len(args) <= 0 {
+		panic(fmt.Sprintf("*** insufficient number of arguments (%d) to function `lastword'.", len(args)))
+	}
+	toks := splitSpaces(ev.evalExpr(strings.Join(args, ",")))
+	if len(toks) == 0 {
+		return ""
+	}
+	return toks[len(toks)-1]
+}
 
 // http://www.gnu.org/software/make/manual/make.html#File-Name-Functions
 func funcWildcard(ev *Evaluator, args []string) string {
diff --git a/test/err_word_non_numeric.mk b/test/err_word_non_numeric.mk
new file mode 100644
index 0000000..aacdee9
--- /dev/null
+++ b/test/err_word_non_numeric.mk
@@ -0,0 +1,2 @@
+test:
+	echo $(word -1, foo bar)
diff --git a/test/err_word_zero.mk b/test/err_word_zero.mk
new file mode 100644
index 0000000..9b6efee
--- /dev/null
+++ b/test/err_word_zero.mk
@@ -0,0 +1,2 @@
+test:
+	echo $(word 0, foo bar)
diff --git a/test/firstword.mk b/test/firstword.mk
new file mode 100644
index 0000000..7f9590a
--- /dev/null
+++ b/test/firstword.mk
@@ -0,0 +1,4 @@
+test:
+	echo $(firstword foo bar baz)
+	echo $(firstword )
+
diff --git a/test/lastword.mk b/test/lastword.mk
new file mode 100644
index 0000000..12a2621
--- /dev/null
+++ b/test/lastword.mk
@@ -0,0 +1,4 @@
+test:
+	echo $(lastword foo bar baz)
+	echo $(lastword )
+
diff --git a/test/word.mk b/test/word.mk
new file mode 100644
index 0000000..55f6226
--- /dev/null
+++ b/test/word.mk
@@ -0,0 +1,7 @@
+test:
+	echo $(word 2, foo bar baz)
+	echo $(word 2, )
+	echo $(word 4, foo bar baz)
+	echo $(word 1, foo,bar baz)
+	echo $(word 2, foo,bar baz)
+	echo $(word 2, foo, bar baz)
diff --git a/test/wordlist.mk b/test/wordlist.mk
new file mode 100644
index 0000000..3dc84ed
--- /dev/null
+++ b/test/wordlist.mk
@@ -0,0 +1,5 @@
+test:
+	echo $(wordlist 2, 3, foo bar baz)
+	echo $(wordlist 2, 4, foo bar baz)
+	echo $(wordlist 4, 7, foo bar baz)
+	echo $(wordlist 3, 2, foo bar baz)
diff --git a/test/words.mk b/test/words.mk
new file mode 100644
index 0000000..96f9e4d
--- /dev/null
+++ b/test/words.mk
@@ -0,0 +1,3 @@
+test:
+	echo $(words foo bar baz)
+	echo $(words )