1// Copyright 2015 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package bidi
6
7import (
8	"flag"
9	"fmt"
10	"log"
11	"strconv"
12	"strings"
13	"testing"
14
15	"golang.org/x/text/internal/gen"
16	"golang.org/x/text/internal/testtext"
17	"golang.org/x/text/internal/ucd"
18	"golang.org/x/text/unicode/norm"
19)
20
21var testLevels = flag.Bool("levels", false, "enable testing of levels")
22
23// TestBidiCore performs the tests in BidiTest.txt.
24// See https://www.unicode.org/Public/UCD/latest/ucd/BidiTest.txt.
25func TestBidiCore(t *testing.T) {
26	testtext.SkipIfNotLong(t)
27
28	r := gen.OpenUCDFile("BidiTest.txt")
29	defer r.Close()
30
31	var wantLevels, wantOrder []string
32	p := ucd.New(r, ucd.Part(func(p *ucd.Parser) {
33		s := strings.Split(p.String(0), ":")
34		switch s[0] {
35		case "Levels":
36			wantLevels = strings.Fields(s[1])
37		case "Reorder":
38			wantOrder = strings.Fields(s[1])
39		default:
40			log.Fatalf("Unknown part %q.", s[0])
41		}
42	}))
43
44	for p.Next() {
45		types := []Class{}
46		for _, s := range p.Strings(0) {
47			types = append(types, bidiClass[s])
48		}
49		// We ignore the bracketing part of the algorithm.
50		pairTypes := make([]bracketType, len(types))
51		pairValues := make([]rune, len(types))
52
53		for i := uint(0); i < 3; i++ {
54			if p.Uint(1)&(1<<i) == 0 {
55				continue
56			}
57			lev := level(int(i) - 1)
58			par := newParagraph(types, pairTypes, pairValues, lev)
59
60			if *testLevels {
61				levels := par.getLevels([]int{len(types)})
62				for i, s := range wantLevels {
63					if s == "x" {
64						continue
65					}
66					l, _ := strconv.ParseUint(s, 10, 8)
67					if level(l)&1 != levels[i]&1 {
68						t.Errorf("%s:%d:levels: got %v; want %v", p.String(0), lev, levels, wantLevels)
69						break
70					}
71				}
72			}
73
74			order := par.getReordering([]int{len(types)})
75			gotOrder := filterOrder(types, order)
76			if got, want := fmt.Sprint(gotOrder), fmt.Sprint(wantOrder); got != want {
77				t.Errorf("%s:%d:order: got %v; want %v\noriginal %v", p.String(0), lev, got, want, order)
78			}
79		}
80	}
81	if err := p.Err(); err != nil {
82		log.Fatal(err)
83	}
84}
85
86var removeClasses = map[Class]bool{
87	LRO: true,
88	RLO: true,
89	RLE: true,
90	LRE: true,
91	PDF: true,
92	BN:  true,
93}
94
95// TestBidiCharacters performs the tests in BidiCharacterTest.txt.
96// See https://www.unicode.org/Public/UCD/latest/ucd/BidiCharacterTest.txt
97func TestBidiCharacters(t *testing.T) {
98	testtext.SkipIfNotLong(t)
99
100	ucd.Parse(gen.OpenUCDFile("BidiCharacterTest.txt"), func(p *ucd.Parser) {
101		var (
102			types      []Class
103			pairTypes  []bracketType
104			pairValues []rune
105			parLevel   level
106
107			wantLevel       = level(p.Int(2))
108			wantLevels      = p.Strings(3)
109			wantVisualOrder = p.Strings(4)
110		)
111
112		switch l := p.Int(1); l {
113		case 0, 1:
114			parLevel = level(l)
115		case 2:
116			parLevel = implicitLevel
117		default:
118			// Spec says to ignore unknown parts.
119		}
120
121		runes := p.Runes(0)
122
123		for _, r := range runes {
124			// Assign the bracket type.
125			if d := norm.NFKD.PropertiesString(string(r)).Decomposition(); d != nil {
126				r = []rune(string(d))[0]
127			}
128			p, _ := LookupRune(r)
129
130			// Assign the class for this rune.
131			types = append(types, p.Class())
132
133			switch {
134			case !p.IsBracket():
135				pairTypes = append(pairTypes, bpNone)
136				pairValues = append(pairValues, 0)
137			case p.IsOpeningBracket():
138				pairTypes = append(pairTypes, bpOpen)
139				pairValues = append(pairValues, r)
140			default:
141				pairTypes = append(pairTypes, bpClose)
142				pairValues = append(pairValues, p.reverseBracket(r))
143			}
144		}
145		par := newParagraph(types, pairTypes, pairValues, parLevel)
146
147		// Test results:
148		if got := par.embeddingLevel; got != wantLevel {
149			t.Errorf("%v:level: got %d; want %d", string(runes), got, wantLevel)
150		}
151
152		if *testLevels {
153			gotLevels := getLevelStrings(types, par.getLevels([]int{len(types)}))
154			if got, want := fmt.Sprint(gotLevels), fmt.Sprint(wantLevels); got != want {
155				t.Errorf("%04X %q:%d: got %v; want %v\nval: %x\npair: %v", runes, string(runes), parLevel, got, want, pairValues, pairTypes)
156			}
157		}
158
159		order := par.getReordering([]int{len(types)})
160		order = filterOrder(types, order)
161		if got, want := fmt.Sprint(order), fmt.Sprint(wantVisualOrder); got != want {
162			t.Errorf("%04X %q:%d: got %v; want %v\ngot order: %s", runes, string(runes), parLevel, got, want, reorder(runes, order))
163		}
164	})
165}
166
167func getLevelStrings(cl []Class, levels []level) []string {
168	var results []string
169	for i, l := range levels {
170		if !removeClasses[cl[i]] {
171			results = append(results, fmt.Sprint(l))
172		} else {
173			results = append(results, "x")
174		}
175	}
176	return results
177}
178
179func filterOrder(cl []Class, order []int) []int {
180	no := []int{}
181	for _, o := range order {
182		if !removeClasses[cl[o]] {
183			no = append(no, o)
184		}
185	}
186	return no
187}
188
189func reorder(r []rune, order []int) string {
190	nr := make([]rune, len(order))
191	for i, o := range order {
192		nr[i] = r[o]
193	}
194	return string(nr)
195}
196
197// bidiClass names and codes taken from class "bc" in
198// https://www.unicode.org/Public/8.0.0/ucd/PropertyValueAliases.txt
199var bidiClass = map[string]Class{
200	"AL":  AL,  // classArabicLetter,
201	"AN":  AN,  // classArabicNumber,
202	"B":   B,   // classParagraphSeparator,
203	"BN":  BN,  // classBoundaryNeutral,
204	"CS":  CS,  // classCommonSeparator,
205	"EN":  EN,  // classEuropeanNumber,
206	"ES":  ES,  // classEuropeanSeparator,
207	"ET":  ET,  // classEuropeanTerminator,
208	"L":   L,   // classLeftToRight,
209	"NSM": NSM, // classNonspacingMark,
210	"ON":  ON,  // classOtherNeutral,
211	"R":   R,   // classRightToLeft,
212	"S":   S,   // classSegmentSeparator,
213	"WS":  WS,  // classWhiteSpace,
214
215	"LRO": LRO, // classLeftToRightOverride,
216	"RLO": RLO, // classRightToLeftOverride,
217	"LRE": LRE, // classLeftToRightEmbedding,
218	"RLE": RLE, // classRightToLeftEmbedding,
219	"PDF": PDF, // classPopDirectionalFormat,
220	"LRI": LRI, // classLeftToRightIsolate,
221	"RLI": RLI, // classRightToLeftIsolate,
222	"FSI": FSI, // classFirstStrongIsolate,
223	"PDI": PDI, // classPopDirectionalIsolate,
224}
225