-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
term.go
2652 lines (2334 loc) · 59.6 KB
/
term.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2016 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package ast
import (
"bytes"
"encoding/json"
"fmt"
"io"
"math/big"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"github.com/OneOfOne/xxhash"
"github.com/pkg/errors"
"github.com/open-policy-agent/opa/ast/location"
"github.com/open-policy-agent/opa/util"
)
var errFindNotFound = fmt.Errorf("find: not found")
// Location records a position in source code.
type Location = location.Location
// NewLocation returns a new Location object.
func NewLocation(text []byte, file string, row int, col int) *Location {
return location.NewLocation(text, file, row, col)
}
// Value declares the common interface for all Term values. Every kind of Term value
// in the language is represented as a type that implements this interface:
//
// - Null, Boolean, Number, String
// - Object, Array, Set
// - Variables, References
// - Array, Set, and Object Comprehensions
// - Calls
type Value interface {
Compare(other Value) int // Compare returns <0, 0, or >0 if this Value is less than, equal to, or greater than other, respectively.
Find(path Ref) (Value, error) // Find returns value referred to by path or an error if path is not found.
Hash() int // Returns hash code of the value.
IsGround() bool // IsGround returns true if this value is not a variable or contains no variables.
String() string // String returns a human readable string representation of the value.
}
// InterfaceToValue converts a native Go value x to a Value.
func InterfaceToValue(x interface{}) (Value, error) {
switch x := x.(type) {
case nil:
return Null{}, nil
case bool:
return Boolean(x), nil
case json.Number:
return Number(x), nil
case int64:
return int64Number(x), nil
case uint64:
return uint64Number(x), nil
case float64:
return floatNumber(x), nil
case int:
return intNumber(x), nil
case string:
return String(x), nil
case []interface{}:
r := make([]*Term, len(x))
for i, e := range x {
e, err := InterfaceToValue(e)
if err != nil {
return nil, err
}
r[i] = &Term{Value: e}
}
return NewArray(r...), nil
case map[string]interface{}:
r := newobject(len(x))
for k, v := range x {
k, err := InterfaceToValue(k)
if err != nil {
return nil, err
}
v, err := InterfaceToValue(v)
if err != nil {
return nil, err
}
r.Insert(NewTerm(k), NewTerm(v))
}
return r, nil
case map[string]string:
r := newobject(len(x))
for k, v := range x {
k, err := InterfaceToValue(k)
if err != nil {
return nil, err
}
v, err := InterfaceToValue(v)
if err != nil {
return nil, err
}
r.Insert(NewTerm(k), NewTerm(v))
}
return r, nil
default:
return nil, fmt.Errorf("ast: illegal value: %T", x)
}
}
// ValueFromReader returns an AST value from a JSON serialized value in the reader.
func ValueFromReader(r io.Reader) (Value, error) {
var x interface{}
if err := util.NewJSONDecoder(r).Decode(&x); err != nil {
return nil, err
}
return InterfaceToValue(x)
}
// As converts v into a Go native type referred to by x.
func As(v Value, x interface{}) error {
return util.NewJSONDecoder(bytes.NewBufferString(v.String())).Decode(x)
}
// Resolver defines the interface for resolving references to native Go values.
type Resolver interface {
Resolve(ref Ref) (value interface{}, err error)
}
// ValueResolver defines the interface for resolving references to AST values.
type ValueResolver interface {
Resolve(ref Ref) (value Value, err error)
}
// UnknownValueErr indicates a ValueResolver was unable to resolve a reference
// because the reference refers to an unknown value.
type UnknownValueErr struct{}
func (UnknownValueErr) Error() string {
return "unknown value"
}
// IsUnknownValueErr returns true if the err is an UnknownValueErr.
func IsUnknownValueErr(err error) bool {
_, ok := err.(UnknownValueErr)
return ok
}
type illegalResolver struct{}
func (illegalResolver) Resolve(ref Ref) (interface{}, error) {
return nil, fmt.Errorf("illegal value: %v", ref)
}
// ValueToInterface returns the Go representation of an AST value. The AST
// value should not contain any values that require evaluation (e.g., vars,
// comprehensions, etc.)
func ValueToInterface(v Value, resolver Resolver) (interface{}, error) {
switch v := v.(type) {
case Null:
return nil, nil
case Boolean:
return bool(v), nil
case Number:
return json.Number(v), nil
case String:
return string(v), nil
case *Array:
buf := []interface{}{}
for i := 0; i < v.Len(); i++ {
x1, err := ValueToInterface(v.Elem(i).Value, resolver)
if err != nil {
return nil, err
}
buf = append(buf, x1)
}
return buf, nil
case *object:
buf := make(map[string]interface{}, v.Len())
err := v.Iter(func(k, v *Term) error {
ki, err := ValueToInterface(k.Value, resolver)
if err != nil {
return err
}
var str string
var ok bool
if str, ok = ki.(string); !ok {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(ki); err != nil {
return err
}
str = strings.TrimSpace(buf.String())
}
vi, err := ValueToInterface(v.Value, resolver)
if err != nil {
return err
}
buf[str] = vi
return nil
})
if err != nil {
return nil, err
}
return buf, nil
case Set:
buf := []interface{}{}
err := v.Iter(func(x *Term) error {
x1, err := ValueToInterface(x.Value, resolver)
if err != nil {
return err
}
buf = append(buf, x1)
return nil
})
if err != nil {
return nil, err
}
return buf, nil
case Ref:
return resolver.Resolve(v)
default:
return nil, fmt.Errorf("%v requires evaluation", TypeName(v))
}
}
// JSON returns the JSON representation of v. The value must not contain any
// refs or terms that require evaluation (e.g., vars, comprehensions, etc.)
func JSON(v Value) (interface{}, error) {
return ValueToInterface(v, illegalResolver{})
}
// MustInterfaceToValue converts a native Go value x to a Value. If the
// conversion fails, this function will panic. This function is mostly for test
// purposes.
func MustInterfaceToValue(x interface{}) Value {
v, err := InterfaceToValue(x)
if err != nil {
panic(err)
}
return v
}
// Term is an argument to a function.
type Term struct {
Value Value `json:"value"` // the value of the Term as represented in Go
Location *Location `json:"-"` // the location of the Term in the source
}
// NewTerm returns a new Term object.
func NewTerm(v Value) *Term {
return &Term{
Value: v,
}
}
// SetLocation updates the term's Location and returns the term itself.
func (term *Term) SetLocation(loc *Location) *Term {
term.Location = loc
return term
}
// Loc returns the Location of term.
func (term *Term) Loc() *Location {
if term == nil {
return nil
}
return term.Location
}
// SetLoc sets the location on term.
func (term *Term) SetLoc(loc *Location) {
term.SetLocation(loc)
}
// Copy returns a deep copy of term.
func (term *Term) Copy() *Term {
if term == nil {
return nil
}
cpy := *term
switch v := term.Value.(type) {
case Null, Boolean, Number, String, Var:
cpy.Value = v
case Ref:
cpy.Value = v.Copy()
case *Array:
cpy.Value = v.Copy()
case Set:
cpy.Value = v.Copy()
case *object:
cpy.Value = v.Copy()
case *ArrayComprehension:
cpy.Value = v.Copy()
case *ObjectComprehension:
cpy.Value = v.Copy()
case *SetComprehension:
cpy.Value = v.Copy()
case Call:
cpy.Value = v.Copy()
}
return &cpy
}
// Equal returns true if this term equals the other term. Equality is
// defined for each kind of term.
func (term *Term) Equal(other *Term) bool {
if term == nil && other != nil {
return false
}
if term != nil && other == nil {
return false
}
if term == other {
return true
}
// TODO(tsandall): This early-exit avoids allocations for types that have
// Equal() functions that just use == underneath. We should revisit the
// other types and implement Equal() functions that do not require
// allocations.
switch v := term.Value.(type) {
case Null:
return v.Equal(other.Value)
case Boolean:
return v.Equal(other.Value)
case Number:
return v.Equal(other.Value)
case String:
return v.Equal(other.Value)
case Var:
return v.Equal(other.Value)
}
return term.Value.Compare(other.Value) == 0
}
// Get returns a value referred to by name from the term.
func (term *Term) Get(name *Term) *Term {
switch v := term.Value.(type) {
case *Array:
return v.Get(name)
case *object:
return v.Get(name)
case Set:
if v.Contains(name) {
return name
}
}
return nil
}
// Hash returns the hash code of the Term's value.
func (term *Term) Hash() int {
return term.Value.Hash()
}
// IsGround returns true if this terms' Value is ground.
func (term *Term) IsGround() bool {
return term.Value.IsGround()
}
// MarshalJSON returns the JSON encoding of the term.
//
// Specialized marshalling logic is required to include a type hint for Value.
func (term *Term) MarshalJSON() ([]byte, error) {
d := map[string]interface{}{
"type": TypeName(term.Value),
"value": term.Value,
}
return json.Marshal(d)
}
func (term *Term) String() string {
return term.Value.String()
}
// UnmarshalJSON parses the byte array and stores the result in term.
// Specialized unmarshalling is required to handle Value.
func (term *Term) UnmarshalJSON(bs []byte) error {
v := map[string]interface{}{}
if err := util.UnmarshalJSON(bs, &v); err != nil {
return err
}
val, err := unmarshalValue(v)
if err != nil {
return err
}
term.Value = val
return nil
}
// Vars returns a VarSet with variables contained in this term.
func (term *Term) Vars() VarSet {
vis := &VarVisitor{vars: VarSet{}}
vis.Walk(term)
return vis.vars
}
// IsConstant returns true if the AST value is constant.
func IsConstant(v Value) bool {
found := false
vis := GenericVisitor{
func(x interface{}) bool {
switch x.(type) {
case Var, Ref, *ArrayComprehension, *ObjectComprehension, *SetComprehension, Call:
found = true
return true
}
return false
},
}
vis.Walk(v)
return !found
}
// IsComprehension returns true if the supplied value is a comprehension.
func IsComprehension(x Value) bool {
switch x.(type) {
case *ArrayComprehension, *ObjectComprehension, *SetComprehension:
return true
}
return false
}
// ContainsRefs returns true if the Value v contains refs.
func ContainsRefs(v interface{}) bool {
found := false
WalkRefs(v, func(r Ref) bool {
found = true
return found
})
return found
}
// ContainsComprehensions returns true if the Value v contains comprehensions.
func ContainsComprehensions(v interface{}) bool {
found := false
WalkClosures(v, func(x interface{}) bool {
switch x.(type) {
case *ArrayComprehension, *ObjectComprehension, *SetComprehension:
found = true
return found
}
return found
})
return found
}
// IsScalar returns true if the AST value is a scalar.
func IsScalar(v Value) bool {
switch v.(type) {
case String:
return true
case Number:
return true
case Boolean:
return true
case Null:
return true
}
return false
}
// Null represents the null value defined by JSON.
type Null struct{}
// NullTerm creates a new Term with a Null value.
func NullTerm() *Term {
return &Term{Value: Null{}}
}
// Equal returns true if the other term Value is also Null.
func (null Null) Equal(other Value) bool {
switch other.(type) {
case Null:
return true
default:
return false
}
}
// Compare compares null to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (null Null) Compare(other Value) int {
return Compare(null, other)
}
// Find returns the current value or a not found error.
func (null Null) Find(path Ref) (Value, error) {
if len(path) == 0 {
return null, nil
}
return nil, errFindNotFound
}
// Hash returns the hash code for the Value.
func (null Null) Hash() int {
return 0
}
// IsGround always returns true.
func (null Null) IsGround() bool {
return true
}
func (null Null) String() string {
return "null"
}
// Boolean represents a boolean value defined by JSON.
type Boolean bool
// BooleanTerm creates a new Term with a Boolean value.
func BooleanTerm(b bool) *Term {
return &Term{Value: Boolean(b)}
}
// Equal returns true if the other Value is a Boolean and is equal.
func (bol Boolean) Equal(other Value) bool {
switch other := other.(type) {
case Boolean:
return bol == other
default:
return false
}
}
// Compare compares bol to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (bol Boolean) Compare(other Value) int {
return Compare(bol, other)
}
// Find returns the current value or a not found error.
func (bol Boolean) Find(path Ref) (Value, error) {
if len(path) == 0 {
return bol, nil
}
return nil, errFindNotFound
}
// Hash returns the hash code for the Value.
func (bol Boolean) Hash() int {
if bol {
return 1
}
return 0
}
// IsGround always returns true.
func (bol Boolean) IsGround() bool {
return true
}
func (bol Boolean) String() string {
return strconv.FormatBool(bool(bol))
}
// Number represents a numeric value as defined by JSON.
type Number json.Number
// NumberTerm creates a new Term with a Number value.
func NumberTerm(n json.Number) *Term {
return &Term{Value: Number(n)}
}
// IntNumberTerm creates a new Term with an integer Number value.
func IntNumberTerm(i int) *Term {
return &Term{Value: Number(strconv.Itoa(i))}
}
// UIntNumberTerm creates a new Term with an unsigned integer Number value.
func UIntNumberTerm(u uint64) *Term {
return &Term{Value: uint64Number(u)}
}
// FloatNumberTerm creates a new Term with a floating point Number value.
func FloatNumberTerm(f float64) *Term {
s := strconv.FormatFloat(f, 'g', -1, 64)
return &Term{Value: Number(s)}
}
// Equal returns true if the other Value is a Number and is equal.
func (num Number) Equal(other Value) bool {
switch other := other.(type) {
case Number:
return Compare(num, other) == 0
default:
return false
}
}
// Compare compares num to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (num Number) Compare(other Value) int {
return Compare(num, other)
}
// Find returns the current value or a not found error.
func (num Number) Find(path Ref) (Value, error) {
if len(path) == 0 {
return num, nil
}
return nil, errFindNotFound
}
// Hash returns the hash code for the Value.
func (num Number) Hash() int {
f, err := json.Number(num).Float64()
if err != nil {
bs := []byte(num)
h := xxhash.Checksum64(bs)
return int(h)
}
return int(f)
}
// Int returns the int representation of num if possible.
func (num Number) Int() (int, bool) {
i64, ok := num.Int64()
return int(i64), ok
}
// Int64 returns the int64 representation of num if possible.
func (num Number) Int64() (int64, bool) {
i, err := json.Number(num).Int64()
if err != nil {
return 0, false
}
return i, true
}
// Float64 returns the float64 representation of num if possible.
func (num Number) Float64() (float64, bool) {
f, err := json.Number(num).Float64()
if err != nil {
return 0, false
}
return f, true
}
// IsGround always returns true.
func (num Number) IsGround() bool {
return true
}
// MarshalJSON returns JSON encoded bytes representing num.
func (num Number) MarshalJSON() ([]byte, error) {
return json.Marshal(json.Number(num))
}
func (num Number) String() string {
return string(num)
}
func intNumber(i int) Number {
return Number(strconv.Itoa(i))
}
func int64Number(i int64) Number {
return Number(strconv.FormatInt(i, 10))
}
func uint64Number(u uint64) Number {
return Number(strconv.FormatUint(u, 10))
}
func floatNumber(f float64) Number {
return Number(strconv.FormatFloat(f, 'g', -1, 64))
}
// String represents a string value as defined by JSON.
type String string
// StringTerm creates a new Term with a String value.
func StringTerm(s string) *Term {
return &Term{Value: String(s)}
}
// Equal returns true if the other Value is a String and is equal.
func (str String) Equal(other Value) bool {
switch other := other.(type) {
case String:
return str == other
default:
return false
}
}
// Compare compares str to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (str String) Compare(other Value) int {
return Compare(str, other)
}
// Find returns the current value or a not found error.
func (str String) Find(path Ref) (Value, error) {
if len(path) == 0 {
return str, nil
}
return nil, errFindNotFound
}
// IsGround always returns true.
func (str String) IsGround() bool {
return true
}
func (str String) String() string {
return strconv.Quote(string(str))
}
// Hash returns the hash code for the Value.
func (str String) Hash() int {
h := xxhash.ChecksumString64S(string(str), hashSeed0)
return int(h)
}
// Var represents a variable as defined by the language.
type Var string
// VarTerm creates a new Term with a Variable value.
func VarTerm(v string) *Term {
return &Term{Value: Var(v)}
}
// Equal returns true if the other Value is a Variable and has the same value
// (name).
func (v Var) Equal(other Value) bool {
switch other := other.(type) {
case Var:
return v == other
default:
return false
}
}
// Compare compares v to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (v Var) Compare(other Value) int {
return Compare(v, other)
}
// Find returns the current value or a not found error.
func (v Var) Find(path Ref) (Value, error) {
if len(path) == 0 {
return v, nil
}
return nil, errFindNotFound
}
// Hash returns the hash code for the Value.
func (v Var) Hash() int {
h := xxhash.ChecksumString64S(string(v), hashSeed0)
return int(h)
}
// IsGround always returns false.
func (v Var) IsGround() bool {
return false
}
// IsWildcard returns true if this is a wildcard variable.
func (v Var) IsWildcard() bool {
return strings.HasPrefix(string(v), WildcardPrefix)
}
// IsGenerated returns true if this variable was generated during compilation.
func (v Var) IsGenerated() bool {
return strings.HasPrefix(string(v), "__local")
}
func (v Var) String() string {
// Special case for wildcard so that string representation is parseable. The
// parser mangles wildcard variables to make their names unique and uses an
// illegal variable name character (WildcardPrefix) to avoid conflicts. When
// we serialize the variable here, we need to make sure it's parseable.
if v.IsWildcard() {
return Wildcard.String()
}
return string(v)
}
// Ref represents a reference as defined by the language.
type Ref []*Term
// EmptyRef returns a new, empty reference.
func EmptyRef() Ref {
return Ref([]*Term{})
}
// PtrRef returns a new reference against the head for the pointer
// s. Path components in the pointer are unescaped.
func PtrRef(head *Term, s string) (Ref, error) {
s = strings.Trim(s, "/")
if s == "" {
return Ref{head}, nil
}
parts := strings.Split(s, "/")
ref := make(Ref, len(parts)+1)
ref[0] = head
for i := 0; i < len(parts); i++ {
var err error
parts[i], err = url.PathUnescape(parts[i])
if err != nil {
return nil, err
}
ref[i+1] = StringTerm(parts[i])
}
return ref, nil
}
// RefTerm creates a new Term with a Ref value.
func RefTerm(r ...*Term) *Term {
return &Term{Value: Ref(r)}
}
// Append returns a copy of ref with the term appended to the end.
func (ref Ref) Append(term *Term) Ref {
n := len(ref)
dst := make(Ref, n+1)
copy(dst, ref)
dst[n] = term
return dst
}
// Insert returns a copy of the ref with x inserted at pos. If pos < len(ref),
// existing elements are shifted to the right. If pos > len(ref)+1 this
// function panics.
func (ref Ref) Insert(x *Term, pos int) Ref {
if pos == len(ref) {
return ref.Append(x)
} else if pos > len(ref)+1 {
panic("illegal index")
}
cpy := make(Ref, len(ref)+1)
for i := 0; i < pos; i++ {
cpy[i] = ref[i]
}
cpy[pos] = x
for i := pos; i < len(ref); i++ {
cpy[i+1] = ref[i]
}
return cpy
}
// Extend returns a copy of ref with the terms from other appended. The head of
// other will be converted to a string.
func (ref Ref) Extend(other Ref) Ref {
dst := make(Ref, len(ref)+len(other))
for i := range ref {
dst[i] = ref[i]
}
head := other[0].Copy()
head.Value = String(head.Value.(Var))
offset := len(ref)
dst[offset] = head
for i := range other[1:] {
dst[offset+i+1] = other[i+1]
}
return dst
}
// Concat returns a ref with the terms appended.
func (ref Ref) Concat(terms []*Term) Ref {
if len(terms) == 0 {
return ref
}
cpy := make(Ref, len(ref)+len(terms))
for i := range ref {
cpy[i] = ref[i]
}
for i := range terms {
cpy[len(ref)+i] = terms[i]
}
return cpy
}
// Dynamic returns the offset of the first non-constant operand of ref.
func (ref Ref) Dynamic() int {
switch ref[0].Value.(type) {
case Call:
return 0
}
for i := 1; i < len(ref); i++ {
if !IsConstant(ref[i].Value) {
return i
}
}
return -1
}
// Copy returns a deep copy of ref.
func (ref Ref) Copy() Ref {
return termSliceCopy(ref)
}
// Equal returns true if ref is equal to other.
func (ref Ref) Equal(other Value) bool {
return Compare(ref, other) == 0
}
// Compare compares ref to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (ref Ref) Compare(other Value) int {
return Compare(ref, other)
}
// Find returns the current value or a not found error.
func (ref Ref) Find(path Ref) (Value, error) {
if len(path) == 0 {
return ref, nil
}
return nil, errFindNotFound
}
// Hash returns the hash code for the Value.
func (ref Ref) Hash() int {
return termSliceHash(ref)
}
// HasPrefix returns true if the other ref is a prefix of this ref.
func (ref Ref) HasPrefix(other Ref) bool {
if len(other) > len(ref) {
return false
}
for i := range other {
if !ref[i].Equal(other[i]) {
return false
}
}
return true
}
// ConstantPrefix returns the constant portion of the ref starting from the head.
func (ref Ref) ConstantPrefix() Ref {
ref = ref.Copy()
i := ref.Dynamic()
if i < 0 {
return ref
}
return ref[:i]
}
// GroundPrefix returns the ground portion of the ref starting from the head. By
// definition, the head of the reference is always ground.
func (ref Ref) GroundPrefix() Ref {
prefix := make(Ref, 0, len(ref))
for i, x := range ref {
if i > 0 && !x.IsGround() {
break
}
prefix = append(prefix, x)
}
return prefix
}
// IsGround returns true if all of the parts of the Ref are ground.
func (ref Ref) IsGround() bool {
if len(ref) == 0 {
return true
}
return termSliceIsGround(ref[1:])
}
// IsNested returns true if this ref contains other Refs.
func (ref Ref) IsNested() bool {
for _, x := range ref {
if _, ok := x.Value.(Ref); ok {
return true
}
}
return false
}
// Ptr returns a slash-separated path string for this ref. If the ref
// contains non-string terms this function returns an error. Path
// components are escaped.
func (ref Ref) Ptr() (string, error) {
parts := make([]string, 0, len(ref)-1)
for _, term := range ref[1:] {
if str, ok := term.Value.(String); ok {
parts = append(parts, url.PathEscape(string(str)))
} else {
return "", fmt.Errorf("invalid path value type")
}
}
return strings.Join(parts, "/"), nil
}