Skip to content

Commit

Permalink
internal/core: support field value aliases
Browse files Browse the repository at this point in the history
Support aliases of the form:
   a: X=b

This implementation also allows for general alias values,
but these have not yet been implemented as alias
declarations are still being deprecated.

Issue #620
Issue #380

Change-Id: Ide4c888bea187042898e8123480a1cfeb909914c
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9543
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
  • Loading branch information
mpvl committed Apr 30, 2021
1 parent 67c6b6f commit 8823e2a
Show file tree
Hide file tree
Showing 14 changed files with 627 additions and 142 deletions.
21 changes: 18 additions & 3 deletions cue/ast/astutil/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,12 @@ type ErrFunc func(pos token.Pos, msg string, args ...interface{})
// Let Clause File/Struct LetClause
// Alias declaration File/Struct Alias (deprecated)
// Illegal Reference File/Struct
// Value
// X in a: X=y Field Alias
// Fields
// X in X: y File/Struct Expr (y)
// X in X=x: y File/Struct Field
// X in X=(x): y File/Struct Field
// X in X="\(x)": y File/Struct Field
// X in [X=x]: y Field Expr (x)
// X in X=[x]: y Field Field
Expand Down Expand Up @@ -143,7 +146,12 @@ func newScope(f *ast.File, outer *scope, node ast.Node, decls []ast.Decl) *scope
// default:
name, isIdent, _ := ast.LabelName(label)
if isIdent {
s.insert(name, x.Value, x)
v := x.Value
// Avoid interpreting value aliases at this point.
if a, ok := v.(*ast.Alias); ok {
v = a.Expr
}
s.insert(name, v, x)
}
case *ast.LetClause:
name, isIdent, _ := ast.LabelName(x.Ident)
Expand Down Expand Up @@ -335,9 +343,16 @@ func (s *scope) Before(n ast.Node) (w visitor) {
}
}

if x.Value != nil {
if n := x.Value; n != nil {
if alias, ok := x.Value.(*ast.Alias); ok {
// TODO: this should move into Before once decl attributes
// have been fully deprecated and embed attributes are introduced.
s = newScope(s.file, s, x, nil)
s.insert(alias.Ident.Name, alias, x)
n = alias.Expr
}
s.inField = true
walk(s, x.Value)
walk(s, n)
s.inField = false
}

Expand Down
106 changes: 46 additions & 60 deletions cue/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -846,73 +846,62 @@ func (p *parser) parseField() (decl ast.Decl) {
this := &ast.Field{Label: nil}
m := this

for i := 0; ; i++ {
tok := p.tok

label, expr, decl, ok := p.parseLabel(false)
if decl != nil {
return decl
}
m.Label = label
tok := p.tok

if !ok {
if expr == nil {
expr = p.parseRHS()
}
if a, ok := expr.(*ast.Alias); ok {
if i > 0 {
p.errorExpected(p.pos, "label or ':'")
return &ast.BadDecl{From: pos, To: p.pos}
}
p.consumeDeclComma()
return a
}
e := &ast.EmbedDecl{Expr: expr}
p.consumeDeclComma()
return e
}
label, expr, decl, ok := p.parseLabel(false)
if decl != nil {
return decl
}
m.Label = label

if p.tok == token.OPTION {
m.Optional = p.pos
p.next()
if !ok {
if expr == nil {
expr = p.parseRHS()
}

if p.tok == token.COLON || p.tok == token.ISA {
break
if a, ok := expr.(*ast.Alias); ok {
p.consumeDeclComma()
return a
}
e := &ast.EmbedDecl{Expr: expr}
p.consumeDeclComma()
return e
}

// TODO: consider disallowing comprehensions with more than one label.
// This can be a bit awkward in some cases, but it would naturally
// enforce the proper style that a comprehension be defined in the
// smallest possible scope.
// allowComprehension = false
if p.tok == token.OPTION {
m.Optional = p.pos
p.next()
}

switch p.tok {
case token.COMMA:
p.expectComma() // sync parser.
fallthrough
// TODO: consider disallowing comprehensions with more than one label.
// This can be a bit awkward in some cases, but it would naturally
// enforce the proper style that a comprehension be defined in the
// smallest possible scope.
// allowComprehension = false

case token.RBRACE, token.EOF:
if i == 0 {
if a, ok := expr.(*ast.Alias); ok {
p.assertV0(p.pos, 1, 3, `old-style alias; use "let X = expr"`)
switch p.tok {
case token.COLON, token.ISA:
case token.COMMA:
p.expectComma() // sync parser.
fallthrough

return a
}
switch tok {
case token.IDENT, token.LBRACK, token.LPAREN,
token.STRING, token.INTERPOLATION,
token.NULL, token.TRUE, token.FALSE,
token.FOR, token.IF, token.LET, token.IN:
return &ast.EmbedDecl{Expr: expr}
}
}
fallthrough
case token.RBRACE, token.EOF:
if a, ok := expr.(*ast.Alias); ok {
p.assertV0(p.pos, 1, 3, `old-style alias; use "let X = expr"`)

default:
p.errorExpected(p.pos, "label or ':'")
return &ast.BadDecl{From: pos, To: p.pos}
return a
}
switch tok {
case token.IDENT, token.LBRACK, token.LPAREN,
token.STRING, token.INTERPOLATION,
token.NULL, token.TRUE, token.FALSE,
token.FOR, token.IF, token.LET, token.IN:
return &ast.EmbedDecl{Expr: expr}
}
fallthrough

default:
p.errorExpected(p.pos, "label or ':'")
return &ast.BadDecl{From: pos, To: p.pos}
}

m.TokenPos = p.pos
Expand All @@ -936,9 +925,6 @@ func (p *parser) parseField() (decl ast.Decl) {
if expr == nil {
expr = p.parseRHS()
}
if a, ok := expr.(*ast.Alias); ok {
p.errf(expr.Pos(), "alias %q not allowed as value", debugStr(a.Ident))
}
m.Value = expr
break
}
Expand Down
12 changes: 11 additions & 1 deletion cue/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,16 @@ func TestParse(t *testing.T) {
a: { a: 1 }
}`,
"{[foo=_]: {a: int}, a: {a: 1}}",
}, {
"value alias",
`
{
a: X=foo
b: Y={foo}
c: d: e: X=5
}
`,
`{a: X=foo, b: Y={foo}, c: {d: {e: X=5}}}`,
}, {
"dynamic labels",
`{
Expand Down Expand Up @@ -567,7 +577,7 @@ bar: 2
in: `
a: int=>2
`,
out: "a: int=>2\nalias \"int\" not allowed as value",
out: "a: int=>2",
}, {
desc: "struct comments",
in: `
Expand Down
46 changes: 46 additions & 0 deletions cue/testdata/references/value.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
-- in.cue --
structShorthand: X={b: 3, c: X.b}

// Note that X and Y are subtly different, as they have different bindings:
// one binds to the field, the other to the value. In this case, that does not
// make a difference.
fieldAndValue: X=foo: Y={ 3, #sum: X + Y }

valueCycle: b: X=3+X

-- out/eval --
(struct){
structShorthand: (struct){
b: (int){ 3 }
c: (int){ 3 }
}
fieldAndValue: (struct){
foo: (int){
3
#sum: (int){ 6 }
}
}
valueCycle: (struct){
b: (_|_){
// [cycle] cycle error:
// ./in.cue:8:18
}
}
}
-- out/compile --
--- in.cue
{
structShorthand: {
b: 3
c: 〈1〉.b
}
fieldAndValue: {
foo: {
3
#sum: (〈1;foo〉 + 〈1〉)
}
}
valueCycle: {
b: (3 + 〈0〉)
}
}
4 changes: 4 additions & 0 deletions internal/core/adt/adt.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ func (*Builtin) expr() {}

func (*NodeLink) expr() {}
func (*FieldReference) expr() {}
func (*ValueReference) expr() {}
func (*LabelReference) expr() {}
func (*DynamicReference) expr() {}
func (*ImportReference) expr() {}
Expand Down Expand Up @@ -281,6 +282,8 @@ func (*NodeLink) declNode() {}
func (*NodeLink) elemNode() {}
func (*FieldReference) declNode() {}
func (*FieldReference) elemNode() {}
func (*ValueReference) declNode() {}
func (*ValueReference) elemNode() {}
func (*LabelReference) declNode() {}
func (*LabelReference) elemNode() {}
func (*DynamicReference) declNode() {}
Expand Down Expand Up @@ -338,6 +341,7 @@ func (*ListLit) node() {}
func (*BoundExpr) node() {}
func (*NodeLink) node() {}
func (*FieldReference) node() {}
func (*ValueReference) node() {}
func (*LabelReference) node() {}
func (*DynamicReference) node() {}
func (*ImportReference) node() {}
Expand Down
25 changes: 25 additions & 0 deletions internal/core/adt/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,31 @@ func (x *FieldReference) resolve(c *OpContext, state VertexStatus) *Vertex {
return c.lookup(n, pos, x.Label, state)
}

// A ValueReference represents a lexical reference to a value.
//
// a: X=b
//
type ValueReference struct {
Src *ast.Ident
UpCount int32
Label Feature // for informative purposes
}

func (x *ValueReference) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}

func (x *ValueReference) resolve(c *OpContext, state VertexStatus) *Vertex {
if x.UpCount == 0 {
return c.vertex
}
n := c.relNode(x.UpCount - 1)
return n
}

// A LabelReference refers to the string or integer value of a label.
//
// [X=Pattern]: b: X
Expand Down
Loading

0 comments on commit 8823e2a

Please sign in to comment.