blob: 235bed7bda7d1795f02343249f4278cf09d91c2d [file] [log] [blame]
// Copyright 2019 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 (
"sort"
"strings"
"cuelang.org/go/cue/ast"
"cuelang.org/go/internal"
)
// This file includes functionality for parsing attributes.
// These functions are slightly more permissive than the spec. Together with the
// scanner and parser the full spec is implemented, though.
// attributes is used to store per-key attribute text for a fields.
// It deliberately does not implement the value interface, as it should
// never act as a value in any way.
type attributes struct {
attr []attr
}
type attr struct {
text string
offset int
}
func (a *attr) key() string {
return a.text[1:a.offset]
}
func (a *attr) body() string {
return a.text[a.offset+1 : len(a.text)-1]
}
func createAttrs(ctx *context, src source, attrs []*ast.Attribute) (a *attributes, err *bottom) {
if len(attrs) == 0 {
return nil, nil
}
as := []attr{}
for _, a := range attrs {
index := strings.IndexByte(a.Text, '(')
n := len(a.Text)
if index < 2 || a.Text[0] != '@' || a.Text[n-1] != ')' {
return nil, ctx.mkErr(newNode(a), "invalid attribute %q", a.Text)
}
as = append(as, attr{a.Text[:n], index})
if err := internal.ParseAttrBody(src.Pos(), a.Text[index+1:n-1]).Err; err != nil {
return nil, ctx.mkErr(newNode(a), err)
}
}
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])
return nil, ctx.mkErr(n, "multiple attributes for key %q", ai.key())
}
}
return &attributes{as}, nil
}
// unifyAttrs merges the attributes from a and b. It may return either a or b
// if a and b are identical.
func unifyAttrs(ctx *context, src source, a, b *attributes) (atrs *attributes, err evaluated) {
if a == b {
return a, nil
}
if a == nil {
return b, nil
}
if b == nil {
return a, nil
}
if len(a.attr) == len(b.attr) {
for i, x := range a.attr {
if x != b.attr[i] {
goto notSame
}
}
return a, nil
}
notSame:
as := append(a.attr, b.attr...)
// remove duplicates and error on conflicts
sort.Slice(as, func(i, j int) bool { return as[i].text < as[j].text })
k := 0
for i := 1; i < len(as); i++ {
if ak, ai := as[k], as[i]; ak.key() == ai.key() {
if ak.body() == ai.body() {
continue
}
return nil, ctx.mkErr(src, "conflicting attributes for key %q", ai.key())
}
k++
as[k] = as[i]
}
return &attributes{as[:k+1]}, nil
}
// parsedAttr holds positional information for a single parsedAttr.
type parsedAttr struct {
fields []keyValue
}
type keyValue struct {
data string
equal int // index of equal sign or 0 if non-existing
}
func (kv *keyValue) text() string { return kv.data }
func (kv *keyValue) key() string { return kv.data[:kv.equal] }
func (kv *keyValue) value() string { return kv.data[kv.equal+1:] }