language: added PreferSameScript MatchOption

and added match options.

Change-Id: I7c772740515ff557b30fa03be034622b49ea544a
Reviewed-on: https://go-review.googlesource.com/47592
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/language/match.go b/language/match.go
index 71f1258..63bc744 100644
--- a/language/match.go
+++ b/language/match.go
@@ -6,6 +6,16 @@
 
 import "errors"
 
+// A MatchOption configures a Matcher.
+type MatchOption func(*matcher)
+
+// PreferSameScript will, in the absence of a match, result in the first
+// preferred tag with the same script as a supported tag to match this supported
+// tag. The default is currently true, but this may change in the future.
+func PreferSameScript(preferSame bool) MatchOption {
+	return func(m *matcher) { m.preferSameScript = preferSame }
+}
+
 // Matcher is the interface that wraps the Match method.
 //
 // Match returns the best match for any of the given tags, along with
@@ -36,8 +46,8 @@
 // matched tag in t, but is augmented with the Unicode extension ('u')of the
 // corresponding preferred tag. This allows user locale options to be passed
 // transparently.
-func NewMatcher(t []Tag) Matcher {
-	return newMatcher(t)
+func NewMatcher(t []Tag, options ...MatchOption) Matcher {
+	return newMatcher(t, options)
 }
 
 func (m *matcher) Match(want ...Tag) (t Tag, index int, c Confidence) {
@@ -47,18 +57,20 @@
 	} else {
 		// TODO: this should be an option
 		t = m.default_.tag
-	outer:
-		for _, w := range want {
-			script, _ := w.Script()
-			if script.scriptID == 0 {
-				// Don't do anything if there is no script, such as with
-				// private subtags.
-				continue
-			}
-			for i, h := range m.supported {
-				if script.scriptID == h.maxScript {
-					t, index = h.tag, i
-					break outer
+		if m.preferSameScript {
+		outer:
+			for _, w := range want {
+				script, _ := w.Script()
+				if script.scriptID == 0 {
+					// Don't do anything if there is no script, such as with
+					// private subtags.
+					continue
+				}
+				for i, h := range m.supported {
+					if script.scriptID == h.maxScript {
+						t, index = h.tag, i
+						break outer
+					}
 				}
 			}
 		}
@@ -407,10 +419,11 @@
 
 // matcher keeps a set of supported language tags, indexed by language.
 type matcher struct {
-	default_     *haveTag
-	supported    []*haveTag
-	index        map[langID]*matchHeader
-	passSettings bool
+	default_         *haveTag
+	supported        []*haveTag
+	index            map[langID]*matchHeader
+	passSettings     bool
+	preferSameScript bool
 }
 
 // matchHeader has the lists of tags for exact matches and matches based on
@@ -521,9 +534,13 @@
 // newMatcher builds an index for the given supported tags and returns it as
 // a matcher. It also expands the index by considering various equivalence classes
 // for a given tag.
-func newMatcher(supported []Tag) *matcher {
+func newMatcher(supported []Tag, options []MatchOption) *matcher {
 	m := &matcher{
-		index: make(map[langID]*matchHeader),
+		index:            make(map[langID]*matchHeader),
+		preferSameScript: true,
+	}
+	for _, o := range options {
+		o(m)
 	}
 	if len(supported) == 0 {
 		m.default_ = &haveTag{}
diff --git a/language/match_test.go b/language/match_test.go
index cbb847c..4fb56a0 100644
--- a/language/match_test.go
+++ b/language/match_test.go
@@ -381,7 +381,7 @@
 func TestBestMatch(t *testing.T) {
 	for _, tt := range matchTests {
 		supported := parseSupported(tt.supported)
-		m := newMatcher(supported)
+		m := newMatcher(supported, nil)
 		if *verbose {
 			fmt.Printf("%s:\n%v\n", tt.comment, m)
 		}
@@ -475,7 +475,7 @@
 }
 
 func BenchmarkMatch(b *testing.B) {
-	m := newMatcher(benchHave)
+	m := newMatcher(benchHave, nil)
 	for i := 0; i < b.N; i++ {
 		for _, want := range benchWant {
 			m.getBest(want...)
@@ -485,7 +485,7 @@
 
 func BenchmarkMatchExact(b *testing.B) {
 	want := mk("en")
-	m := newMatcher(benchHave)
+	m := newMatcher(benchHave, nil)
 	for i := 0; i < b.N; i++ {
 		m.getBest(want)
 	}
@@ -493,7 +493,7 @@
 
 func BenchmarkMatchAltLanguagePresent(b *testing.B) {
 	want := mk("hr")
-	m := newMatcher(benchHave)
+	m := newMatcher(benchHave, nil)
 	for i := 0; i < b.N; i++ {
 		m.getBest(want)
 	}
@@ -501,7 +501,7 @@
 
 func BenchmarkMatchAltLanguageNotPresent(b *testing.B) {
 	want := mk("nn")
-	m := newMatcher(benchHave)
+	m := newMatcher(benchHave, nil)
 	for i := 0; i < b.N; i++ {
 		m.getBest(want)
 	}
@@ -509,7 +509,7 @@
 
 func BenchmarkMatchAltScriptPresent(b *testing.B) {
 	want := mk("zh-Hant-CN")
-	m := newMatcher(benchHave)
+	m := newMatcher(benchHave, nil)
 	for i := 0; i < b.N; i++ {
 		m.getBest(want)
 	}
@@ -517,7 +517,7 @@
 
 func BenchmarkMatchAltScriptNotPresent(b *testing.B) {
 	want := mk("fr-Cyrl")
-	m := newMatcher(benchHave)
+	m := newMatcher(benchHave, nil)
 	for i := 0; i < b.N; i++ {
 		m.getBest(want)
 	}
@@ -525,7 +525,7 @@
 
 func BenchmarkMatchLimitedExact(b *testing.B) {
 	want := []Tag{mk("he-NL"), mk("iw-NL")}
-	m := newMatcher(benchHave)
+	m := newMatcher(benchHave, nil)
 	for i := 0; i < b.N; i++ {
 		m.getBest(want...)
 	}