blob: 98195d990c25e2d2809a4ad874f7484c473063c2 [file] [log] [blame]
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +01001// Copyright 2018 The CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// This file implements scopes and the objects they contain.
16
17package parser
18
19import (
20 "bytes"
21 "fmt"
22
23 "cuelang.org/go/cue/ast"
24 "cuelang.org/go/cue/token"
25)
26
27// resolve resolves all identifiers in a file. Unresolved identifiers are
28// recorded in Unresolved.
29func resolve(f *ast.File, errFn func(pos token.Pos, msg string)) {
30 walk(&scope{errFn: errFn}, f)
31}
32
33// A Scope maintains the set of named language entities declared
34// in the scope and a link to the immediately surrounding (outer)
35// scope.
36//
37type scope struct {
38 file *ast.File
39 outer *scope
40 node ast.Node
41 index map[string]ast.Node
42
43 errFn func(p token.Pos, msg string)
44}
45
46func newScope(f *ast.File, outer *scope, node ast.Node, decls []ast.Decl) *scope {
47 const n = 4 // initial scope capacity
48 s := &scope{
49 file: f,
50 outer: outer,
51 node: node,
52 index: make(map[string]ast.Node, n),
53 errFn: outer.errFn,
54 }
55 for _, d := range decls {
56 switch x := d.(type) {
57 case *ast.Field:
58 if name, ok := ast.LabelName(x.Label); ok {
59 s.insert(name, x.Value)
60 }
61 case *ast.Alias:
62 name := x.Ident.Name
63 s.insert(name, x)
64 // Handle imports
65 }
66 }
67 return s
68}
69
70func (s *scope) insert(name string, n ast.Node) {
71 if _, existing := s.lookup(name); existing != nil {
72 _, isAlias1 := n.(*ast.Alias)
73 _, isAlias2 := existing.(*ast.Alias)
74 if isAlias1 != isAlias2 {
75 s.errFn(n.Pos(), "cannot have alias and non-alias with the same name")
76 return
77 } else if isAlias1 || isAlias2 {
78 s.errFn(n.Pos(), "cannot have two aliases with the same name in the same scope")
79 return
80 }
81 }
82 s.index[name] = n
83}
84
85func (s *scope) lookup(name string) (obj, node ast.Node) {
86 last := s
87 for s != nil {
88 if n, ok := s.index[name]; ok {
89 if last.node == n {
90 return nil, n
91 }
92 return s.node, n
93 }
94 s, last = s.outer, s
95 }
96 return nil, nil
97}
98
99func (s *scope) After(n ast.Node) {}
100func (s *scope) Before(n ast.Node) (w visitor) {
101 switch x := n.(type) {
102 case *ast.File:
103 s := newScope(x, s, x, x.Decls)
104 // Support imports.
105 for _, d := range x.Decls {
106 walk(s, d)
107 }
108 return nil
109
110 case *ast.StructLit:
111 return newScope(s.file, s, x, x.Elts)
112
113 case *ast.ComprehensionDecl:
114 s = scopeClauses(s, x.Clauses)
115
116 case *ast.ListComprehension:
117 s = scopeClauses(s, x.Clauses)
118
119 case *ast.Field:
120 switch label := x.Label.(type) {
121 case *ast.Interpolation:
122 walk(s, label)
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100123 case *ast.TemplateLabel:
124 s := newScope(s.file, s, x, nil)
125 name, _ := ast.LabelName(label)
126 s.insert(name, x.Label) // Field used for entire lambda.
127 walk(s, x.Value)
128 return nil
129 }
130 // Disallow referring to the current LHS name (this applies recursively)
131 if x.Value != nil {
132 walk(s, x.Value)
133 }
134 return nil
135
136 case *ast.Alias:
137 // Disallow referring to the current LHS name.
138 name := x.Ident.Name
139 saved := s.index[name]
140 delete(s.index, name) // The same name may still appear in another scope
141
142 if x.Expr != nil {
143 walk(s, x.Expr)
144 }
145 s.index[name] = saved
146 return nil
147
148 case *ast.ImportSpec:
149 return nil
150
151 case *ast.SelectorExpr:
152 walk(s, x.X)
153 return nil
154
Marcel van Lohuizend96ad3d2018-12-10 15:30:20 +0100155 case *ast.Ident:
156 if obj, node := s.lookup(x.Name); node != nil {
157 x.Node = node
158 x.Scope = obj
159 } else {
160 s.file.Unresolved = append(s.file.Unresolved, x)
161 }
162 return nil
163 }
164 return s
165}
166
167func scopeClauses(s *scope, clauses []ast.Clause) *scope {
168 for _, c := range clauses {
169 if f, ok := c.(*ast.ForClause); ok { // TODO(let): support let clause
170 walk(s, f.Source)
171 s = newScope(s.file, s, f, nil)
172 if f.Key != nil {
173 s.insert(f.Key.Name, f.Key)
174 }
175 s.insert(f.Value.Name, f.Value)
176 } else {
177 walk(s, c)
178 }
179 }
180 return s
181}
182
183// Debugging support
184func (s *scope) String() string {
185 var buf bytes.Buffer
186 fmt.Fprintf(&buf, "scope %p {", s)
187 if s != nil && len(s.index) > 0 {
188 fmt.Fprintln(&buf)
189 for name := range s.index {
190 fmt.Fprintf(&buf, "\t%v\n", name)
191 }
192 }
193 fmt.Fprintf(&buf, "}\n")
194 return buf.String()
195}