blob: bc2a32ca19ec4cede82dd2970bdd031c2829fcfb [file] [log] [blame]
// Copyright 2020 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 adt
import (
"strconv"
"strings"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/errors"
"cuelang.org/go/cue/token"
"cuelang.org/go/internal"
)
// A Feature is an encoded form of a label which comprises a compact
// representation of an integer or string label as well as a label type.
type Feature uint32
// TODO: create labels such that list are sorted first (or last with index.)
// InvalidLabel is an encoding of an erroneous label.
const InvalidLabel Feature = 0x7 // 0xb111
// MaxIndex indicates the maximum number of unique strings that are used for
// labeles within this CUE implementation.
const MaxIndex int64 = 1<<28 - 1
// A StringIndexer coverts strings to and from an index that is unique for a
// given string.
type StringIndexer interface {
// ToIndex returns a unique positive index for s (0 < index < 2^28-1).
//
// For each pair of strings s and t it must return the same index if and
// only if s == t.
StringToIndex(s string) (index int64)
// ToString returns a string s for index such that ToIndex(s) == index.
IndexToString(index int64) string
}
// SelectorString reports the shortest string representation of f when used as a
// selector.
func (f Feature) SelectorString(index StringIndexer) string {
if f == 0 {
return "_"
}
x := f.Index()
switch f.Typ() {
case IntLabel:
return strconv.Itoa(int(x))
case StringLabel:
s := index.IndexToString(int64(x))
if ast.IsValidIdent(s) && !internal.IsDefOrHidden(s) {
return s
}
return strconv.Quote(s)
default:
return index.IndexToString(int64(x))
}
}
// StringValue reports the string value of f, which must be a string label.
func (f Feature) StringValue(index StringIndexer) string {
if !f.IsString() {
panic("not a string label")
}
x := f.Index()
return index.IndexToString(int64(x))
}
// ToValue converts a label to a value, which will be a Num for integer labels
// and a String for string labels. It panics when f is not a regular label.
func (f Feature) ToValue(ctx *OpContext) Value {
if !f.IsRegular() {
panic("not a regular label")
}
if f.IsInt() {
return ctx.NewInt64(int64(f.Index()))
}
x := f.Index()
str := ctx.IndexToString(int64(x))
return ctx.NewString(str)
}
// StringLabel converts s to a string label.
func (c *OpContext) StringLabel(s string) Feature {
return labelFromValue(c, &String{Str: s})
}
// MakeStringLabel creates a label for the given string.
func MakeStringLabel(r StringIndexer, s string) Feature {
i := r.StringToIndex(s)
// TODO: set position if it exists.
f, err := MakeLabel(nil, i, StringLabel)
if err != nil {
panic("out of free string slots")
}
return f
}
// MakeIdentLabel creates a label for the given identifier.
func MakeIdentLabel(r StringIndexer, s string) Feature {
i := r.StringToIndex(s)
t := StringLabel
switch {
case strings.HasPrefix(s, "#_"):
t = HiddenDefinitionLabel
case strings.HasPrefix(s, "#"):
t = DefinitionLabel
case strings.HasPrefix(s, "_"):
t = HiddenLabel
}
f, err := MakeLabel(nil, i, t)
if err != nil {
panic("out of free string slots")
}
return f
}
const msgGround = "invalid non-ground value %s (must be concrete %s)"
func labelFromValue(ctx *OpContext, v Value) Feature {
var i int64
var t FeatureType
if isError(v) {
return InvalidLabel
}
switch v.Kind() {
case IntKind, NumKind:
x, _ := v.(*Num)
if x == nil {
ctx.addErrf(IncompleteError, pos(v), msgGround, v, "int")
return InvalidLabel
}
t = IntLabel
var err error
i, err = x.X.Int64()
if err != nil || x.K != IntKind {
ctx.AddErrf("invalid label %v: %v", v, err)
return InvalidLabel
}
if i < 0 {
ctx.AddErrf("invalid negative index %s", ctx.Str(x))
return InvalidLabel
}
case StringKind:
x, _ := v.(*String)
if x == nil {
ctx.addErrf(IncompleteError, pos(v), msgGround, v, "string")
return InvalidLabel
}
t = StringLabel
i = ctx.StringToIndex(x.Str)
default:
ctx.AddErrf("invalid label type %v", v.Kind())
return InvalidLabel
}
// TODO: set position if it exists.
f, err := MakeLabel(nil, i, t)
if err != nil {
ctx.AddErr(err)
}
return f
}
// MakeLabel creates a label. It reports an error if the index is out of range.
func MakeLabel(src ast.Node, index int64, f FeatureType) (Feature, errors.Error) {
if 0 > index || index > MaxIndex {
p := token.NoPos
if src != nil {
p = src.Pos()
}
return InvalidLabel,
errors.Newf(p, "int label out of range (%d not >=0 and <= %d)",
index, MaxIndex)
}
return Feature(index)<<indexShift | Feature(f), nil
}
// A FeatureType indicates the type of label.
type FeatureType int8
const (
StringLabel FeatureType = 0 // 0b000
IntLabel FeatureType = 1 // 0b001
DefinitionLabel FeatureType = 3 // 0b011
HiddenLabel FeatureType = 6 // 0b110
HiddenDefinitionLabel FeatureType = 7 // 0b111
// letLabel FeatureType = 0b010
fTypeMask Feature = 7 // 0b111
indexShift = 3
)
// IsValid reports whether f is a valid label.
func (f Feature) IsValid() bool { return f != InvalidLabel }
// Typ reports the type of label.
func (f Feature) Typ() FeatureType { return FeatureType(f & fTypeMask) }
// IsRegular reports whether a label represents a data field.
func (f Feature) IsRegular() bool { return f.Typ() <= IntLabel }
// IsString reports whether a label represents a regular field.
func (f Feature) IsString() bool { return f.Typ() == StringLabel }
// IsDef reports whether the label is a definition (an identifier starting with
// # or #_.
func (f Feature) IsDef() bool {
if f == InvalidLabel {
// TODO(perf): do more mask trickery to avoid this branch.
return false
}
return f.Typ()&DefinitionLabel == DefinitionLabel
}
// IsInt reports whether this is an integer index.
func (f Feature) IsInt() bool { return f.Typ() == IntLabel }
// IsHidden reports whether this label is hidden (an identifier starting with
// _ or #_).
func (f Feature) IsHidden() bool {
if f == InvalidLabel {
// TODO(perf): do more mask trickery to avoid this branch.
return false
}
return f.Typ()&HiddenLabel == HiddenLabel
}
// Index reports the abstract index associated with f.
func (f Feature) Index() int { return int(f >> indexShift) }
// TODO: should let declarations be implemented as fields?
// func (f Feature) isLet() bool { return f.typ() == letLabel }