cue/parser: add support for failing on legacy grammar
Implement test in tutorial to detect legacy usage.
Issue #87
Change-Id: Id2aa0e928029b199acc7a9d77e38402a348f61d4
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3549
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/parser/interface.go b/cue/parser/interface.go
index 25b5af3..ce09af3 100644
--- a/cue/parser/interface.go
+++ b/cue/parser/interface.go
@@ -71,6 +71,28 @@
}
)
+// FromVersion specifies until which legacy version the parser should provide
+// backwards compatibility.
+func FromVersion(version int) Option {
+ if version >= 0 {
+ version++
+ }
+ // Versions:
+ // <0: major version 0 (counting -1000 + x, where x = 100*m+p in 0.m.p
+ // >=0: x+1 in 1.x.y
+ return func(p *parser) { p.version = version }
+}
+
+func version0(minor, patch int) int {
+ return -1000 + 100*minor + patch
+}
+
+// Latest specifies the latest version of the parser, effectively setting
+// the strictest implementation.
+const Latest = latest
+
+const latest = 1000
+
// FileOffset specifies the File position info to use.
func FileOffset(pos int) Option {
return func(p *parser) { p.offset = pos }
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 2e99cb1..894e2a7 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -60,6 +60,7 @@
imports []*ast.ImportSpec // list of imports
+ version int
}
func (p *parser) init(filename string, src []byte, mode []Option) {
@@ -255,6 +256,8 @@
// Scan the comment for '\n' chars and adjust endline accordingly.
endline = p.file.Line(p.pos)
if p.lit[1] == '*' {
+ p.assertV0(0, 10, "block quotes")
+
// don't use range here - no need to decode Unicode code points
for i := 0; i < len(p.lit); i++ {
if p.lit[i] == '\n' {
@@ -346,6 +349,12 @@
}
}
+func (p *parser) assertV0(minor, patch int, name string) {
+ if p.version != 0 && p.version > version0(minor, patch) {
+ p.errf(p.pos, "%s deprecated as of v0.%d.%d", name, minor, patch+1)
+ }
+}
+
func (p *parser) errf(pos token.Pos, msg string, args ...interface{}) {
// ePos := p.file.Position(pos)
ePos := pos
diff --git a/cue/parser/parser_test.go b/cue/parser/parser_test.go
index 79bf9a8..85e56c9 100644
--- a/cue/parser/parser_test.go
+++ b/cue/parser/parser_test.go
@@ -408,6 +408,22 @@
}
}
+func TestStrict(t *testing.T) {
+ testCases := []struct{ desc, in string }{
+ {"block comments",
+ `a: 1 /* a */`},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ mode := []Option{AllErrors, ParseComments, FromVersion(Latest)}
+ _, err := ParseFile("input", tc.in, mode...)
+ if err == nil {
+ t.Errorf("unexpected success: %v", tc.in)
+ }
+ })
+ }
+}
+
func TestParseExpr(t *testing.T) {
// just kicking the tires:
// a valid arithmetic expression
diff --git a/cue/value.go b/cue/value.go
index d1a1b1d..519408b 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -76,7 +76,7 @@
}
got := x.kind()
if got&want&concreteKind == bottomKind && want != bottomKind {
- return ctx.mkErr(x, "cannot use value %v (type %s) as %s", x, got, want)
+ return ctx.mkErr(x, "cannot use value %v (type %s) as %s", ctx.str(x), got, want)
}
if !got.isGround() {
return ctx.mkErr(x, codeIncomplete,
diff --git a/doc/tutorial/basics/script_test.go b/doc/tutorial/basics/script_test.go
index 92e2179..6b68152 100644
--- a/doc/tutorial/basics/script_test.go
+++ b/doc/tutorial/basics/script_test.go
@@ -3,15 +3,49 @@
import (
"flag"
"os"
+ "path"
"path/filepath"
+ "strings"
"testing"
"cuelang.org/go/cmd/cue/cmd"
+ "cuelang.org/go/cue/parser"
"github.com/rogpeppe/testscript"
+ "github.com/rogpeppe/testscript/txtar"
)
var update = flag.Bool("update", false, "update the test files")
+// TestLatest checks that the examples match the latest language standard,
+// even if still valid in backwards compatibility mode.
+func TestLatest(t *testing.T) {
+ filepath.Walk(".", func(fullpath string, info os.FileInfo, err error) error {
+ if !strings.HasSuffix(fullpath, ".txt") {
+ return nil
+ }
+
+ a, err := txtar.ParseFile(fullpath)
+ if err != nil {
+ t.Error(err)
+ return nil
+ }
+
+ for _, f := range a.Files {
+ t.Run(path.Join(fullpath, f.Name), func(t *testing.T) {
+ if !strings.HasSuffix(f.Name, ".cue") {
+ return
+ }
+ v := parser.FromVersion(parser.Latest)
+ _, err := parser.ParseFile(f.Name, f.Data, v)
+ if err != nil {
+ t.Errorf("%v: %v", fullpath, err)
+ }
+ })
+ }
+ return nil
+ })
+}
+
func TestScript(t *testing.T) {
filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {