cue: implement attribute declarations
This only allows attributes in places that can
clearly be considered to be part of the
package fields. The spec has been adjusted
accordingly.
Issue #259
Change-Id: I3c8b65554096502577b48082c638c662eee98ac5
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4823
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/ast.go b/cue/ast.go
index c83397b..0e43a64 100644
--- a/cue/ast.go
+++ b/cue/ast.go
@@ -280,6 +280,9 @@
case *ast.Comprehension:
v1.walk(x)
+
+ case *ast.Attribute:
+ // Nothing to do.
}
}
if v.ctx().inDefinition > 0 && !obj.optionals.isFull() {
diff --git a/cue/ast/ast.go b/cue/ast/ast.go
index b95714c..4138969 100644
--- a/cue/ast/ast.go
+++ b/cue/ast/ast.go
@@ -282,6 +282,7 @@
Text string // must be a valid attribute format.
comments
+ decl
}
func (a *Attribute) Pos() token.Pos { return a.At }
diff --git a/cue/ast/astutil/resolve.go b/cue/ast/astutil/resolve.go
index af8cbd3..9195ca9 100644
--- a/cue/ast/astutil/resolve.go
+++ b/cue/ast/astutil/resolve.go
@@ -276,6 +276,10 @@
case *ast.ImportSpec:
return nil
+ case *ast.Attribute:
+ // TODO: tokenize attributes, resolve identifiers and store the ones
+ // that resolve in a list.
+
case *ast.SelectorExpr:
walk(s, x.X)
return nil
diff --git a/cue/attr.go b/cue/attr.go
index 636fa05..03ec8d3 100644
--- a/cue/attr.go
+++ b/cue/attr.go
@@ -63,7 +63,8 @@
}
}
- sort.Slice(as, func(i, j int) bool { return as[i].text < as[j].text })
+ sort.SliceStable(as, func(i, j int) bool { return as[i].text < as[j].text })
+ // TODO: remove these restrictions.
for i := 1; i < len(as); i++ {
if ai, aj := as[i-1], as[i]; ai.key() == aj.key() {
n := newNode(attrs[0])
diff --git a/cue/attr_test.go b/cue/attr_test.go
index 5f1f7f0..c48ce96 100644
--- a/cue/attr_test.go
+++ b/cue/attr_test.go
@@ -146,7 +146,7 @@
attrs, err := createAttrs(&context{}, baseValue{}, a)
if tc.err != "" {
- if !strings.Contains(debugStr(&context{}, err), tc.err) {
+ if err == nil || !strings.Contains(debugStr(&context{}, err), tc.err) {
t.Errorf("error was %v; want %v", err, tc.err)
}
return
diff --git a/cue/format/node.go b/cue/format/node.go
index 86309f4..3972a49 100644
--- a/cue/format/node.go
+++ b/cue/format/node.go
@@ -352,6 +352,9 @@
f.expr(n.Expr)
f.print(declcomma) // implied
+ case *ast.Attribute:
+ f.print(n.At, n)
+
case *ast.CommentGroup:
f.print(newsection)
f.printComment(n)
diff --git a/cue/format/testdata/simplify.golden b/cue/format/testdata/simplify.golden
index 929063b..53f7a18 100644
--- a/cue/format/testdata/simplify.golden
+++ b/cue/format/testdata/simplify.golden
@@ -6,6 +6,8 @@
"a.b": "foo-": cc_dd: x
+@attr(3)
+
a: b: c: 3
// references to bar are all shadowed and this can be safely turned into
@@ -26,10 +28,13 @@
"_foo": 3
x: {
+ @tag0(foo)
r1: baz1
bar: r2: bar
- r3: bar
- E=quux: 3
+ r3: bar
+ E=quux: 3
+
+ @tag1(bar)
r4: quux
[baz2="str"]: 4
r5: baz2
diff --git a/cue/format/testdata/simplify.input b/cue/format/testdata/simplify.input
index 4bb466a..d642ae1 100644
--- a/cue/format/testdata/simplify.input
+++ b/cue/format/testdata/simplify.input
@@ -6,6 +6,7 @@
"a.b": "foo-": "cc_dd": x
+@attr(3)
a:
b:
@@ -29,10 +30,13 @@
"_foo": 3
x: {
+@tag0(foo)
r1: baz1
bar: r2: bar
r3: bar
E=quux: 3
+
+ @tag1(bar)
r4: quux
[baz2="str"]: 4
r5: baz2
diff --git a/cue/load/import_test.go b/cue/load/import_test.go
index 294de7f..96984ad 100644
--- a/cue/load/import_test.go
+++ b/cue/load/import_test.go
@@ -60,7 +60,7 @@
var e *NoFilesError
ok := xerrors.As(err, &e)
if !ok {
- t.Fatal(`Import("testdata/ignored") did not return NoCUEError.`)
+ t.Fatal(`Import("testdata/ignored") did not return NoFilesError.`)
}
if !e.ignored {
t.Fatal(`Import("testdata/ignored") should have ignored CUE files.`)
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 7ef6ef1..177513d 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -684,9 +684,17 @@
case token.FOR, token.IF, token.LET:
list = append(list, p.parseComprehension())
+ case token.ATTRIBUTE:
+ list = append(list, p.parseAttribute())
+ if p.atComma("struct literal", token.RBRACE) { // TODO: may be EOF
+ p.next()
+ }
+
default:
list = append(list, p.parseField())
}
+
+ // TODO: handle next comma here, after disallowing non-colon separator.
}
if p.tok == token.ELLIPSIS {
@@ -948,16 +956,30 @@
func (p *parser) parseAttributes() (attrs []*ast.Attribute) {
p.openList()
for p.tok == token.ATTRIBUTE {
- c := p.openComments()
- a := &ast.Attribute{At: p.pos, Text: p.lit}
- p.next()
- c.closeNode(p, a)
- attrs = append(attrs, a)
+ attrs = append(attrs, p.parseAttribute())
}
p.closeList()
return attrs
}
+func (p *parser) parseAttributeDecls() (a []ast.Decl) {
+ for p.tok == token.ATTRIBUTE {
+ a = append(a, p.parseAttribute())
+ if p.atComma("struct literal", token.RBRACE) { // TODO: may be EOF
+ p.next()
+ }
+ }
+ return a
+}
+
+func (p *parser) parseAttribute() *ast.Attribute {
+ c := p.openComments()
+ a := &ast.Attribute{At: p.pos, Text: p.lit}
+ p.next()
+ c.closeNode(p, a)
+ return a
+}
+
func (p *parser) parseLabel(rhs bool) (label ast.Label, expr ast.Expr, ok bool) {
tok := p.tok
switch tok {
diff --git a/cue/parser/parser_test.go b/cue/parser/parser_test.go
index e5eb5b6..1082dbc 100644
--- a/cue/parser/parser_test.go
+++ b/cue/parser/parser_test.go
@@ -471,6 +471,24 @@
import . "foo"
`,
out: "import , \"foo\"\nexpected 'STRING', found '.'",
+ }, {
+ desc: "attributes",
+ in: `
+ package name
+
+ @t1(v1)
+
+ {
+ @t2(v2)
+ }
+ a: {
+ a: 1
+ @t3(v3)
+ @t4(v4)
+ c: 2
+ }
+ `,
+ out: "package name, @t1(v1), {@t2(v2)}, a: {a: 1, @t3(v3), @t4(v4), c: 2}",
}}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
diff --git a/doc/ref/spec.md b/doc/ref/spec.md
index a9e207b..c1fc803 100644
--- a/doc/ref/spec.md
+++ b/doc/ref/spec.md
@@ -2842,7 +2842,7 @@
to a data format
```
-SourceFile = { attribute "," } [ PackageClause "," ] { ImportDecl "," } { Declaration "," } .
+SourceFile = [ PackageClause "," ] { ImportDecl "," } { Declaration "," } .
```
```