diff --git a/demo/gop-parser/natural-lang/nlang.gop b/demo/gop-parser/natural-lang/nlang.gop
new file mode 100644
index 000000000..f61708b7d
--- /dev/null
+++ b/demo/gop-parser/natural-lang/nlang.gop
@@ -0,0 +1,18 @@
+import "os"
+
+cl := tpl`
+expr = subject verb object
+subject = "I" | "You" | "He" | "She" | "It" | "Dog" | "Cat"
+object = "me" | "you" | "him" | "her" | "it" | "fish" | "apple" | "banana" | "dog" | "cat"
+verb = "attack" | "love" | "eat" | "hate"
+`!
+
+print "> "
+for line <- os.Stdin {
+	e, err := cl.parseExpr(line, nil)
+	if err != nil {
+		print "${err}\n> "
+	} else {
+		print e, "\n > "
+	}
+}
diff --git a/tpl/matcher/match.go b/tpl/matcher/match.go
index 40b9767ae..15278cb9d 100644
--- a/tpl/matcher/match.go
+++ b/tpl/matcher/match.go
@@ -24,6 +24,13 @@ import (
 	"github.com/goplus/gop/tpl/types"
 )
 
+var (
+	errMultiMismatch = errors.New("multiple mismatch")
+
+	// ErrVarAssigned error
+	ErrVarAssigned = errors.New("variable is already assigned")
+)
+
 // -----------------------------------------------------------------------------
 
 // Error represents a matching error.
@@ -114,30 +121,25 @@ type gChoice struct {
 }
 
 func (p *gChoice) Match(src []*types.Token, ctx *Context) (n int, result any, err error) {
-	var nMax int
+	var nMax = -1
 	var errMax error
-	// var multiErr = true
+	var multiErr = true
 
 	for _, g := range p.options {
 		if n, result, err = g.Match(src, ctx); err == nil {
 			return
 		}
 		if n >= nMax {
-			nMax, errMax = n, err
-			/*
-				if n == nMax {
-					multiErr = true
-				} else {
-					nMax, errMax, multiErr = n, err, false
-				}
-			*/
+			if n == nMax {
+				multiErr = true
+			} else {
+				nMax, errMax, multiErr = n, err, false
+			}
 		}
 	}
-	/*
-		if multiErr {
-			errMax = ctx.NewError(src[nMax].End(), "TODO: error msg") // TODO(xsw)
-		}
-	*/
+	if multiErr {
+		errMax = errMultiMismatch
+	}
 	return nMax, nil, errMax
 }
 
@@ -263,11 +265,6 @@ func List(a, b Matcher) Matcher {
 
 // -----------------------------------------------------------------------------
 
-var (
-	// ErrVarAssigned error
-	ErrVarAssigned = errors.New("variable is already assigned")
-)
-
 type Var struct {
 	Elem Matcher
 	Name string
@@ -277,9 +274,20 @@ type Var struct {
 func (p *Var) Match(src []*types.Token, ctx *Context) (n int, result any, err error) {
 	g := p.Elem
 	if g == nil {
-		return 0, nil, ctx.NewErrorf(p.Pos, "variable `%s` not found", p.Name)
+		return 0, nil, ctx.NewErrorf(p.Pos, "variable `%s` not assigned", p.Name)
+	}
+	n, result, err = g.Match(src, ctx)
+	if err == errMultiMismatch {
+		var posErr token.Pos
+		var tokErr any
+		if len(src) > 0 {
+			posErr, tokErr = src[0].Pos, src[0]
+		} else {
+			posErr, tokErr = ctx.FileEnd, "EOF"
+		}
+		err = ctx.NewErrorf(posErr, "expect `%s`, but got `%s`", p.Name, tokErr)
 	}
-	return g.Match(src, ctx)
+	return
 }
 
 // Assign assigns a value to this variable.
diff --git a/tpl/tpl.go b/tpl/tpl.go
index a0bd58677..20da7f22e 100644
--- a/tpl/tpl.go
+++ b/tpl/tpl.go
@@ -166,15 +166,17 @@ func isPlain(result []any) bool {
 
 // -----------------------------------------------------------------------------
 
-func Dump(result any) {
-	Fdump(os.Stdout, result, "", "  ")
+func Dump(result any, omitSemi ...bool) {
+	Fdump(os.Stdout, result, "", "  ", omitSemi != nil && omitSemi[0])
 }
 
-func Fdump(w io.Writer, result any, prefix, indent string) {
+func Fdump(w io.Writer, result any, prefix, indent string, omitSemi bool) {
 	switch result := result.(type) {
 	case *Token:
 		if result.Tok != token.SEMICOLON {
 			fmt.Fprint(w, prefix, result, "\n")
+		} else if !omitSemi {
+			fmt.Fprint(w, prefix, ";\n")
 		}
 	case []any:
 		if isPlain(result) {
@@ -189,7 +191,7 @@ func Fdump(w io.Writer, result any, prefix, indent string) {
 		} else {
 			fmt.Print(prefix, "[\n")
 			for _, v := range result {
-				Fdump(w, v, prefix+indent, indent)
+				Fdump(w, v, prefix+indent, indent, omitSemi)
 			}
 			fmt.Print(prefix, "]\n")
 		}