225 lines
8.1 KiB
Go
225 lines
8.1 KiB
Go
package typeset
|
|
|
|
import "slices"
|
|
import "testing"
|
|
|
|
func rl (run rune) runeLayout { return runeLayout { run: run } }
|
|
func tk (kind tokenKind, value string) token {
|
|
tok := token {
|
|
kind: kind,
|
|
}
|
|
runeValue := []rune(value)
|
|
tok.runes = make([]runeLayout, len(runeValue))
|
|
for index, run := range runeValue {
|
|
tok.runes[index] = rl(run)
|
|
}
|
|
return tok
|
|
}
|
|
func compareTokens (got, correct []token) bool {
|
|
for index, tok := range got {
|
|
correctTok := correct[index]
|
|
isCorrect :=
|
|
correctTok.kind == tok.kind &&
|
|
correctTok.width == tok.width &&
|
|
slices.Equal(correctTok.runes, tok.runes)
|
|
if !isCorrect { return false }
|
|
}
|
|
return true
|
|
}
|
|
func logTokens (test *testing.T, tokens []token) {
|
|
for _, token := range tokens {
|
|
test.Logf("- %-40v | %v", token, token.runes)
|
|
}
|
|
}
|
|
|
|
func TestParseString (test *testing.T) {
|
|
// ---- processing ----
|
|
runes, tokens := parseString("hello \rworld!\nfoo\n\r\nbar\tbaz\t\tsomething")
|
|
|
|
// ---- correct data ----
|
|
correctRunes := []runeLayout {
|
|
rl('h'),
|
|
rl('e'),
|
|
rl('l'),
|
|
rl('l'),
|
|
rl('o'),
|
|
rl(' '),
|
|
rl('\r'),
|
|
rl('w'),
|
|
rl('o'),
|
|
rl('r'),
|
|
rl('l'),
|
|
rl('d'),
|
|
rl('!'),
|
|
rl('\n'),
|
|
rl('f'),
|
|
rl('o'),
|
|
rl('o'),
|
|
rl('\n'),
|
|
rl('\r'),
|
|
rl('\n'),
|
|
rl('b'),
|
|
rl('a'),
|
|
rl('r'),
|
|
rl('\t'),
|
|
rl('b'),
|
|
rl('a'),
|
|
rl('z'),
|
|
rl('\t'),
|
|
rl('\t'),
|
|
rl('s'),
|
|
rl('o'),
|
|
rl('m'),
|
|
rl('e'),
|
|
rl('t'),
|
|
rl('h'),
|
|
rl('i'),
|
|
rl('n'),
|
|
rl('g'),
|
|
}
|
|
correctTokens := []token {
|
|
tk(tokenKindWord, "hello"),
|
|
tk(tokenKindSpace, " "),
|
|
tk(tokenKindWord, "\rworld!"),
|
|
tk(tokenKindLineBreak, "\n"),
|
|
tk(tokenKindWord, "foo"),
|
|
tk(tokenKindLineBreak, "\n"),
|
|
tk(tokenKindLineBreak, "\r\n"),
|
|
tk(tokenKindWord, "bar"),
|
|
tk(tokenKindTab, "\t"),
|
|
tk(tokenKindWord, "baz"),
|
|
tk(tokenKindTab, "\t\t"),
|
|
tk(tokenKindWord, "something"),
|
|
}
|
|
|
|
// ---- testing ----
|
|
if len(runes) != len(correctRunes) {
|
|
test.Logf("len(runes) != len(correctRunes): %d, %d", len(runes), len(correctRunes))
|
|
test.Log(runes)
|
|
test.Log(correctRunes)
|
|
test.FailNow()
|
|
}
|
|
if !slices.Equal(runes, correctRunes) {
|
|
test.Log("runes != correctRunes:")
|
|
test.Log(runes)
|
|
test.Log(correctRunes)
|
|
test.FailNow()
|
|
}
|
|
if len(tokens) != len(correctTokens) {
|
|
test.Logf("len(tokens) != len(correctTokens): %d, %d", len(tokens), len(correctTokens))
|
|
test.Log("GOT")
|
|
logTokens(test, tokens)
|
|
test.Log("CORRECT")
|
|
logTokens(test, correctTokens)
|
|
test.FailNow()
|
|
}
|
|
if !compareTokens(tokens, correctTokens) {
|
|
test.Log("tokens != correctTokens:")
|
|
test.Log("GOT")
|
|
logTokens(test, tokens)
|
|
test.Log("CORRECT")
|
|
logTokens(test, correctTokens)
|
|
test.FailNow()
|
|
}
|
|
test.Logf("changing first rune from %c to x", runes[0].run)
|
|
runes[0].run = 'x'
|
|
test.Logf("first rune is now %c", runes[0].run)
|
|
tokenRune := tokens[0].runes[0].run
|
|
if tokenRune != 'x' {
|
|
test.Fatalf (
|
|
"tokens does not reference the same memory as runes after changing runes: %c, %c",
|
|
runes[0].run, tokenRune)
|
|
}
|
|
runeIndex := 0
|
|
for tokenIndex, token := range tokens {
|
|
tokenRunePtr := &token.runes[0]
|
|
runePtr := &runes[runeIndex]
|
|
|
|
if runePtr != tokenRunePtr {
|
|
test.Fatalf (
|
|
"tokens[%d] does not reference runes[%d]: %p, %p",
|
|
tokenIndex, runeIndex, tokenRunePtr, runePtr)
|
|
}
|
|
|
|
runeIndex += len(token.runes)
|
|
}
|
|
}
|
|
|
|
func BenchmarkParseStringLatin (benchmark *testing.B) {
|
|
benchmark.ReportAllocs()
|
|
var rmeanLen, rmeanCap int
|
|
var tmeanLen, tmeanCap int
|
|
for i := 0; i < benchmark.N; i ++ {
|
|
runes, tokens := parseString(lipsumLt)
|
|
rmeanLen += len(runes)
|
|
rmeanCap += cap(runes)
|
|
tmeanLen += len(tokens)
|
|
tmeanCap += cap(tokens)
|
|
}
|
|
rmeanLen /= benchmark.N
|
|
rmeanCap /= benchmark.N
|
|
tmeanLen /= benchmark.N
|
|
tmeanCap /= benchmark.N
|
|
benchmark.ReportMetric(float64(rmeanCap) / float64(rmeanLen), "rune-waste")
|
|
benchmark.ReportMetric(float64(tmeanCap) / float64(tmeanLen), "token-waste")
|
|
}
|
|
|
|
func BenchmarkParseStringChinese (benchmark *testing.B) {
|
|
benchmark.ReportAllocs()
|
|
var rmeanLen, rmeanCap int
|
|
var tmeanLen, tmeanCap int
|
|
for i := 0; i < benchmark.N; i ++ {
|
|
runes, tokens := parseString(lipsumCn)
|
|
rmeanLen += len(runes)
|
|
rmeanCap += cap(runes)
|
|
tmeanLen += len(tokens)
|
|
tmeanCap += cap(tokens)
|
|
}
|
|
rmeanLen /= benchmark.N
|
|
rmeanCap /= benchmark.N
|
|
tmeanLen /= benchmark.N
|
|
tmeanCap /= benchmark.N
|
|
benchmark.ReportMetric(float64(rmeanCap) / float64(rmeanLen), "rune-waste")
|
|
benchmark.ReportMetric(float64(tmeanCap) / float64(tmeanLen), "token-waste")
|
|
}
|
|
|
|
const lipsumLt =
|
|
`Voluptatem impedit id id facilis et. Sit eligendi aspernatur dicta vitae ipsa officia enim harum. Occaecati quod harum quos temporibus officiis provident enim neque. Odio totam ducimus commodi quis minima ea.
|
|
|
|
Ut delectus quis a rem consectetur laudantium hic sequi. Vel sunt neque nisi excepturi id sit id ut. Dolores expedita et odio. Quibusdam sed et quam nostrum. Sed perspiciatis voluptatibus et.
|
|
|
|
Omnis qui tempore corrupti alias ut repellendus est. A officiis molestias perspiciatis ut dolores nihil. Ut officiis hic quo aut aut dolorum. Modi at molestiae praesentium ea eveniet aut porro.
|
|
|
|
Similique facere cum amet nesciunt dolorem nemo. Rerum temporibus iure maiores. Facere quam nihil quia debitis nihil est officia aliquam. Magnam aut alias consectetur. Velit cumque eligendi assumenda magni ratione. Est dolorem modi a unde.
|
|
|
|
Illo reprehenderit est sunt quaerat cum nihil non. Quia nihil placeat qui ex hic molestiae eligendi. Asperiores optio et nobis et.`
|
|
|
|
const lipsumCn =
|
|
`这很容易并且妨碍快乐。让我们很难为这些人选择上述生活的职责。他们被这样的事实蒙蔽了双眼:他们现在不提供办公室。我讨厌我们给小孩子们带来所有的好处。
|
|
|
|
作为被选中的人,他将跟随这里的赞美。或者除非他们被排除在外,否则他们不是。意想不到的痛苦和仇恨。但对某些人来说,以及我们自己。但让我们看看其中的乐趣和
|
|
|
|
当时所有腐败的人都必须被击退。办公室的麻烦被视为无痛。至于这里的服务无论是哪里还是让人心疼。但目前的麻烦将会发生或持续下去。
|
|
|
|
当没有人知道其中的痛苦时,也要做同样的事情。事物的时代确实更加伟大。无所作为,因为你不欠任何东西,这是一些责任。这将是伟大的或其他方面。无论他选择什么,他都必须非常小心。有一种痛从何而来。
|
|
|
|
他责怪他们在什么都没有的情况下才问。因为没有人喜欢从这里选择麻烦。对我们来说,这是一个更艰难的选择。
|
|
|
|
这很容易并且妨碍快乐。让我们很难为这些人选择上述生活的职责。他们被这样的事实蒙蔽了双眼:他们现在不提供办公室。我讨厌我们给小孩子们带来所有的好处。
|
|
|
|
作为被选中的人,他将跟随这里的赞美。或者除非他们被排除在外,否则他们不是。意想不到的痛苦和仇恨。但对某些人来说,以及我们自己。但让我们看看其中的乐趣和
|
|
|
|
当时所有腐败的人都必须被击退。办公室的麻烦被视为无痛。至于这里的服务无论是哪里还是让人心疼。但目前的麻烦将会发生或持续下去。
|
|
|
|
当没有人知道其中的痛苦时,也要做同样的事情。事物的时代确实更加伟大。无所作为,因为你不欠任何东西,这是一些责任。这将是伟大的或其他方面。无论他选择什么,他都必须非常小心。有一种痛从何而来。
|
|
|
|
他责怪他们在什么都没有的情况下才问。因为没有人喜欢从这里选择麻烦。对我们来说,这是一个更艰难的选择。
|
|
|
|
这很容易并且妨碍快乐。让我们很难为这些人选择上述生活的职责。他们被这样的事实蒙蔽了双眼:他们现在不提供办公室。我讨厌我们给小孩子们带来所有的好处。
|
|
|
|
作为被选中的人,他将跟随这里的赞美。或者除非他们被排除在外,否则他们不是。意想不到的痛苦和仇恨。但对某些人来说,以及我们自己。但让我们看看其中的乐趣和
|
|
|
|
当时所有腐败的人都必须被击退。办公室的麻烦被视为无痛。至于这里的服务无论是哪里还是让人心疼。但目前的麻烦将会发生或持续下去。
|
|
|
|
当没有人知道其中的痛苦时,也要做同样的事情。事物的`
|