blob: 8263be20d2c2aca69c9f9235d8d2cd5c39f668e3 [file] [log] [blame]
// Copyright 2018 The CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cue
import (
"fmt"
"math/big"
"testing"
"cuelang.org/go/cue/ast"
"github.com/cockroachdb/apd"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
func TestUnquote(t *testing.T) {
testCases := []struct {
in, out string
err error
}{
{`"Hello"`, "Hello", nil},
{`'Hello'`, "Hello", nil},
{`'Hellø'`, "Hellø", nil},
{`"""` + "\n\t\tHello\n\t\t" + `"""`, "Hello", nil},
{"'''\n\t\tHello\n\t\t'''", "Hello", nil},
{"'''\n\t\tHello\n\n\t\t'''", "Hello\n", nil},
{"'''\n\n\t\tHello\n\t\t'''", "\nHello", nil},
{"'''\n\n\n\n\t\t'''", "\n\n", nil},
{"'''\n\t\t'''", "", nil},
{`"""` + "\n\raaa\n\rbbb\n\r" + `"""`, "aaa\nbbb", nil},
{`'\a\b\f\n\r\t\v\'\\\/'`, "\a\b\f\n\r\t\v'\\/", nil},
{`"\a\b\f\n\r\t\v\"\\\/"`, "\a\b\f\n\r\t\v\"\\/", nil},
{`#"The sequence "\U0001F604" renders as \#U0001F604."#`,
`The sequence "\U0001F604" renders as 😄.`,
nil},
{`" \U00010FfF"`, " \U00010fff", nil},
{`"\u0061 "`, "a ", nil},
{`'\x61\x55'`, "\x61\x55", nil},
{`'\061\055'`, "\061\055", nil},
{`'\377 '`, "\377 ", nil},
{"'e\u0300\\n'", "e\u0300\n", nil},
{`'\06\055'`, "", errSyntax},
{`'\0'`, "", errSyntax},
{`"\06\055"`, "", errSyntax}, // too short
{`'\777 '`, "", errSyntax}, // overflow
{`'\U012301'`, "", errSyntax}, // too short
{`'\U0123012G'`, "", errSyntax}, // invalid digit G
{`"\x04"`, "", errSyntax}, // not allowed in strings
{`'\U01230123'`, "", errSyntax}, // too large
{`"\\"`, "\\", nil},
{`"\'"`, "", errSyntax},
{`"\q"`, "", errSyntax},
{"'\n'", "", errSyntax},
{"'---\n---'", "", errSyntax},
{"'''\r'''", "", errMissingNewline},
{`#"Hello"#`, "Hello", nil},
{`#"Hello\v"#`, "Hello\\v", nil},
{`#"Hello\#v\r"#`, "Hello\v\\r", nil},
{`##"Hello\##v\r"##`, "Hello\v\\r", nil},
{`##"Hello\##v"##`, "Hello\v", nil},
{"#'''\n\t\tHello\\#v\n\t\t'''#", "Hello\v", nil},
{"##'''\n\t\tHello\\#v\n\t\t'''##", "Hello\\#v", nil},
{`#"""` + "\n\t\t\\#r\n\t\t" + `"""#`, "\r", nil},
{`#""#`, "", nil},
{`#"This is a "dog""#`, `This is a "dog"`, nil},
{"#\"\"\"\n\"\n\"\"\"#", `"`, nil},
{"#\"\"\"\n\"\"\"\n\"\"\"#", `"""`, nil},
{"#\"\"\"\n\na\n\n\"\"\"#", "\na\n", nil},
// Gobble extra \r
{"#\"\"\"\n\ra\n\r\"\"\"#", `a`, nil},
{"#\"\"\"\n\r\n\ra\n\r\n\r\"\"\"#", "\na\n", nil},
// Make sure this works for Windows.
{"#\"\"\"\r\n\r\na\r\n\r\n\"\"\"#", "\na\n", nil},
{"#\"\"\"\r\n \r\n a\r\n \r\n \"\"\"#", "\na\n", nil},
{"#\"\"\"\r\na\r\n\"\"\"#", `a`, nil},
{"#\"\"\"\r\n\ra\r\n\r\"\"\"#", `a`, nil},
{`####" \"####`, ` \`, nil},
{"```", "", errSyntax},
{"Hello", "", errSyntax},
{`"Hello`, "", errUnmatchedQuote},
{`"""Hello"""`, "", errMissingNewline},
{"'''\n Hello\n '''", "", errInvalidWhitespace},
{"'''\n a\n b\n '''", "", errInvalidWhitespace},
{`"Hello""`, "", errSyntax},
{`#"Hello"`, "", errUnmatchedQuote},
{`#"Hello'#`, "", errUnmatchedQuote},
{`#"""#`, "", errMissingNewline},
// TODO: should these be legal?
{`#"""#`, "", errMissingNewline},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d/%s", i, tc.in), func(t *testing.T) {
if got, err := Unquote(tc.in); err != tc.err {
t.Errorf("error: got %q; want %q", err, tc.err)
} else if got != tc.out {
t.Errorf("value: got %q; want %q", got, tc.out)
}
})
}
}
func TestInterpolation(t *testing.T) {
testCases := []struct {
quotes string
in string
out string
err error
}{
{`""`, `foo\(`, "foo", nil},
{`"""` + "\n" + `"""`, `foo`, "", errUnmatchedQuote},
{`#""#`, `foo\#(`, "foo", nil},
{`#""#`, `foo\(`, "", errUnmatchedQuote},
{`""`, `foo\(bar`, "", errSyntax},
{`""`, ``, "", errUnmatchedQuote},
{`#""#`, `"`, "", errUnmatchedQuote},
{`#""#`, `\`, "", errUnmatchedQuote},
{`##""##`, `\'`, "", errUnmatchedQuote},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d/%s/%s", i, tc.quotes, tc.in), func(t *testing.T) {
info, _, _, _ := ParseQuotes(tc.quotes, tc.quotes)
if got, err := info.Unquote(tc.in); err != tc.err {
t.Errorf("error: got %q; want %q", err, tc.err)
} else if got != tc.out {
t.Errorf("value: got %q; want %q", got, tc.out)
}
})
}
}
func TestIsDouble(t *testing.T) {
testCases := []struct {
quotes string
double bool
}{
{`""`, true},
{`"""` + "\n" + `"""`, true},
{`#""#`, true},
{`''`, false},
{`'''` + "\n" + `'''`, false},
{`#''#`, false},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d/%s", i, tc.quotes), func(t *testing.T) {
info, _, _, err := ParseQuotes(tc.quotes, tc.quotes)
if err != nil {
t.Fatal(err)
}
if got := info.IsDouble(); got != tc.double {
t.Errorf("got %v; want %v", got, tc.double)
}
})
}
}
var defIntBase = newNumBase(&ast.BasicLit{}, newNumInfo(numKind, 0, 10, false))
var defRatBase = newNumBase(&ast.BasicLit{}, newNumInfo(floatKind, 0, 10, false))
func mkInt(a int64) *numLit {
x := &numLit{numBase: defIntBase}
x.v.SetInt64(a)
return x
}
func mkIntString(a string) *numLit {
x := &numLit{numBase: defIntBase}
x.v.SetString(a)
return x
}
func mkFloat(a string) *numLit {
x := &numLit{numBase: defRatBase}
x.v.SetString(a)
return x
}
func mkBigInt(a int64) (v apd.Decimal) { v.SetInt64(a); return }
func mkBigFloat(a string) (v apd.Decimal) { v.SetString(a); return }
var diffOpts = []cmp.Option{
cmp.Comparer(func(x, y big.Rat) bool {
return x.String() == y.String()
}),
cmp.Comparer(func(x, y big.Int) bool {
return x.String() == y.String()
}),
cmp.AllowUnexported(
nullLit{},
boolLit{},
stringLit{},
bytesLit{},
numLit{},
numBase{},
numInfo{},
),
cmpopts.IgnoreUnexported(
bottom{},
baseValue{},
baseValue{},
),
}
var (
nullSentinel = &nullLit{}
trueSentinel = &boolLit{b: true}
falseSentinel = &boolLit{b: false}
)
func TestLiterals(t *testing.T) {
mkMul := func(x int64, m multiplier, base int) *numLit {
return &numLit{
newNumBase(&ast.BasicLit{}, newNumInfo(numKind, m, base, false)),
mkBigInt(x),
}
}
hk := &numLit{
newNumBase(&ast.BasicLit{}, newNumInfo(numKind, 0, 10, true)),
mkBigInt(100000),
}
testCases := []struct {
lit string
node value
}{
{"0", mkInt(0)},
{"null", nullSentinel},
{"true", trueSentinel},
{"false", falseSentinel},
{"fls", &bottom{}},
{`"foo"`, &stringLit{str: "foo"}},
{`"\"foo\""`, &stringLit{str: `"foo"`}},
{`"foo\u0032"`, &stringLit{str: `foo2`}},
{`"foo\U00000033"`, &stringLit{str: `foo3`}},
{`"foo\U0001f499"`, &stringLit{str: `foo💙`}},
{`"\a\b\f\n\r\t\v"`, &stringLit{str: "\a\b\f\n\r\t\v"}},
{`"""
"""`, &stringLit{str: ""}},
{`"""
abc
"""`, &stringLit{str: "abc"}},
{`"""
abc
def
"""`, &stringLit{str: "abc\ndef"}},
{`"""
abc
def
"""`, &stringLit{str: "abc\n\tdef"}},
{`'\xff'`, &bytesLit{b: []byte("\xff")}},
{"1", mkInt(1)},
{"100_000", hk},
{"1.", mkFloat("1")},
{"0.0", mkFloat("0.0")},
{".0", mkFloat(".0")},
{"1K", mkMul(1000, mulK, 10)},
{"1Mi", mkMul(1024*1024, mulMi, 10)},
{"1.5Mi", mkMul((1024+512)*1024, mulMi, 10)},
{"1.3Mi", &bottom{}}, // Cannot be accurately represented.
{"1.3G", mkMul(1300000000, mulG, 10)},
{"1.3e+20", mkFloat("1.3e+20")},
{"1.3e20", mkFloat("1.3e+20")},
{"1.3e-5", mkFloat("1.3e-5")},
{"0x1234", mkMul(0x1234, 0, 16)},
{"0xABCD", mkMul(0xABCD, 0, 16)},
{"0b11001000", mkMul(0xc8, 0, 2)},
{"0b1", mkMul(1, 0, 2)},
{"0o755", mkMul(0755, 0, 8)},
}
p := litParser{
ctx: &context{Context: &apd.BaseContext},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d/%+q", i, tc.lit), func(t *testing.T) {
got := p.parse(&ast.BasicLit{Value: tc.lit})
if !cmp.Equal(got, tc.node, diffOpts...) {
t.Error(cmp.Diff(got, tc.node, diffOpts...))
t.Errorf("%#v, %#v\n", got, tc.node)
}
})
}
}
func TestLiteralErrors(t *testing.T) {
testCases := []struct {
lit string
}{
{`"foo\u"`},
{`"foo\u003"`},
{`"foo\U1234567"`},
{`"foo\U12345678"`},
{`"foo\Ug"`},
{`"\xff"`},
// not allowed in string literal, only binary
{`"foo\x00"`},
{`0x`},
{`0o`},
{`0_`},
{``},
{`"`},
{`"a`},
// wrong indentation
{`"""
abc
def
"""`},
// non-matching quotes
{`"""
abc
'''`},
{`"""
abc
"`},
{`"abc \( foo "`},
}
p := litParser{
ctx: &context{Context: &apd.BaseContext},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("%+q", tc.lit), func(t *testing.T) {
got := p.parse(&ast.BasicLit{Value: tc.lit})
if _, ok := got.(*bottom); !ok {
t.Fatalf("expected error but found none")
}
})
}
}