forked from joejulian/gio
6384ab6087
This commit adds a parser for a simple domain-specific language that can express a comma-delimited list of font families within a string. I chose to encode families in this way because the string can be used as an efficient hash key in a way that a slice of families cannot. Similarly, using a slice of families would require allocations on the caller side. The particular format was chosen to allow lists to be written with as little fanfare as possible. This is why quotation marks are completely optional. It's easy to read: Times New Roman, Georgia, serif Why force the user to type this (this will parse the same): "Times New Roman", "Georgia", "serif" I've tried to handle edge cases exhaustively. Commas are legal within quotes. Within a quoted string, you can escape instances of the surrounding quote with a backslash, and can escape literal backslashes by adding another backslash. I wrote the lexer/parser by hand, and I hope that they're both easy to understand and (if need be) extend. A side effect of the DSL I've chosen (and part of my reasoning for allowing both single and double quoted strings) is that CSS font-family rules will generally be valid font family lists in Gio. This means the syntax is already familiar to users coming from other technologies, and that you can copy from a web-based application to get a similar font stack in Gio. Fixes: https://todo.sr.ht/~eliasnaur/gio/317 Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
180 lines
3.4 KiB
Go
180 lines
3.4 KiB
Go
package text
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
func TestParser(t *testing.T) {
|
|
type scenario struct {
|
|
variantName string
|
|
input string
|
|
}
|
|
type testcase struct {
|
|
name string
|
|
inputs []scenario
|
|
expected []string
|
|
shouldErr bool
|
|
}
|
|
|
|
for _, tc := range []testcase{
|
|
{
|
|
name: "empty",
|
|
inputs: []scenario{
|
|
{
|
|
variantName: "",
|
|
},
|
|
},
|
|
shouldErr: true,
|
|
},
|
|
{
|
|
name: "comma failure",
|
|
inputs: []scenario{
|
|
{
|
|
variantName: "bare single",
|
|
input: ",",
|
|
},
|
|
{
|
|
variantName: "bare multiple",
|
|
input: ",, ,,",
|
|
},
|
|
},
|
|
shouldErr: true,
|
|
},
|
|
{
|
|
name: "comma success",
|
|
inputs: []scenario{
|
|
{
|
|
variantName: "squote",
|
|
input: "','",
|
|
},
|
|
{
|
|
variantName: "dquote",
|
|
input: `","`,
|
|
},
|
|
},
|
|
expected: []string{","},
|
|
},
|
|
{
|
|
name: "comma success multiple",
|
|
inputs: []scenario{
|
|
{
|
|
variantName: "squote",
|
|
input: "',,', ',,'",
|
|
},
|
|
{
|
|
variantName: "dquote",
|
|
input: `",,", ",,"`,
|
|
},
|
|
},
|
|
expected: []string{",,", ",,"},
|
|
},
|
|
{
|
|
name: "backslashes",
|
|
inputs: []scenario{
|
|
{
|
|
variantName: "bare",
|
|
input: `\font\\`,
|
|
},
|
|
{
|
|
variantName: "dquote",
|
|
input: `"\\font\\\\"`,
|
|
},
|
|
{
|
|
variantName: "squote",
|
|
input: `'\\font\\\\'`,
|
|
},
|
|
},
|
|
expected: []string{`\font\\`},
|
|
},
|
|
{
|
|
name: "invalid backslashes",
|
|
inputs: []scenario{
|
|
{
|
|
variantName: "dquote",
|
|
input: `"\\""`,
|
|
},
|
|
{
|
|
variantName: "squote",
|
|
input: `'\\''`,
|
|
},
|
|
},
|
|
shouldErr: true,
|
|
},
|
|
{
|
|
name: "too many quotes",
|
|
inputs: []scenario{
|
|
{
|
|
variantName: "dquote",
|
|
input: `"""`,
|
|
},
|
|
{
|
|
variantName: "squote",
|
|
input: `'''`,
|
|
},
|
|
},
|
|
shouldErr: true,
|
|
},
|
|
{
|
|
name: "serif serif's serif\"s",
|
|
inputs: []scenario{
|
|
{
|
|
variantName: "bare",
|
|
input: `serif, serif's, serif"s`,
|
|
},
|
|
{
|
|
variantName: "squote",
|
|
input: `'serif', 'serif\'s', 'serif"s'`,
|
|
},
|
|
{
|
|
variantName: "dquote",
|
|
input: `"serif", "serif's", "serif\"s"`,
|
|
},
|
|
},
|
|
expected: []string{"serif", `serif's`, `serif"s`},
|
|
},
|
|
{
|
|
name: "complex list",
|
|
inputs: []scenario{
|
|
{
|
|
variantName: "bare",
|
|
input: `Times New Roman, Georgia Common, Helvetica Neue, serif`,
|
|
},
|
|
{
|
|
variantName: "squote",
|
|
input: `'Times New Roman', 'Georgia Common', 'Helvetica Neue', 'serif'`,
|
|
},
|
|
{
|
|
variantName: "dquote",
|
|
input: `"Times New Roman", "Georgia Common", "Helvetica Neue", "serif"`,
|
|
},
|
|
{
|
|
variantName: "mixed",
|
|
input: `Times New Roman, "Georgia Common", 'Helvetica Neue', "serif"`,
|
|
},
|
|
{
|
|
variantName: "mixed with weird spacing",
|
|
input: `Times New Roman ,"Georgia Common" , 'Helvetica Neue' ,"serif"`,
|
|
},
|
|
},
|
|
expected: []string{"Times New Roman", "Georgia Common", "Helvetica Neue", "serif"},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
var p parser
|
|
for _, scen := range tc.inputs {
|
|
t.Run(scen.variantName, func(t *testing.T) {
|
|
actual, err := p.parse(scen.input)
|
|
if (err != nil) != tc.shouldErr {
|
|
t.Errorf("unexpected error state: %v", err)
|
|
}
|
|
if !slices.Equal(tc.expected, actual) {
|
|
t.Errorf("expected\n%q\ngot\n%q", tc.expected, actual)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|