blob: 1ce01b22ce3e3e404b1ef34a725673b1e0341253 [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
155 case *ast.LambdaExpr:
156 s = newScope(s.file, s, x, nil)
157 for _, p := range x.Params {
158 name, _ := ast.LabelName(p.Label)
159 s.insert(name, p)
160 if p.Value == nil {
161 // TODO: make this optional
162 p.Value = ast.NewIdent("_")
163 s.insert(name, p)
164 }
165 }
166
167 case *ast.Ident:
168 if obj, node := s.lookup(x.Name); node != nil {
169 x.Node = node
170 x.Scope = obj
171 } else {
172 s.file.Unresolved = append(s.file.Unresolved, x)
173 }
174 return nil
175 }
176 return s
177}
178
179func scopeClauses(s *scope, clauses []ast.Clause) *scope {
180 for _, c := range clauses {
181 if f, ok := c.(*ast.ForClause); ok { // TODO(let): support let clause
182 walk(s, f.Source)
183 s = newScope(s.file, s, f, nil)
184 if f.Key != nil {
185 s.insert(f.Key.Name, f.Key)
186 }
187 s.insert(f.Value.Name, f.Value)
188 } else {
189 walk(s, c)
190 }
191 }
192 return s
193}
194
195// Debugging support
196func (s *scope) String() string {
197 var buf bytes.Buffer
198 fmt.Fprintf(&buf, "scope %p {", s)
199 if s != nil && len(s.index) > 0 {
200 fmt.Fprintln(&buf)
201 for name := range s.index {
202 fmt.Fprintf(&buf, "\t%v\n", name)
203 }
204 }
205 fmt.Fprintf(&buf, "}\n")
206 return buf.String()
207}