OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 class LibraryImport { | |
6 final String prefix; | |
7 final Library library; | |
8 final SourceSpan span; | |
9 LibraryImport(this.library, [this.prefix, this.span]); | |
10 } | |
11 | |
12 // TODO(jimhug): Make this more useful for good error messages. | |
13 class AmbiguousMember extends Member { | |
14 List<Member> members; | |
15 AmbiguousMember(String name, this.members): super(name, null); | |
16 } | |
17 | |
18 | |
19 /** Represents a Dart library. */ | |
20 class Library extends Element { | |
21 final SourceFile baseSource; | |
22 Map<String, DefinedType> types; | |
23 List<LibraryImport> imports; | |
24 String sourceDir; | |
25 List<SourceFile> natives; | |
26 List<SourceFile> sources; | |
27 | |
28 Map<String, Member> _topNames; | |
29 Map<String, MemberSet> _privateMembers; | |
30 | |
31 /** The type that holds top level types in the library. */ | |
32 DefinedType topType; | |
33 | |
34 /** Set to true by [WorldGenerator] once this type has been written. */ | |
35 bool isWritten = false; | |
36 | |
37 Library(this.baseSource) : super(null, null) { | |
38 sourceDir = dirname(baseSource.filename); | |
39 topType = new DefinedType(null, this, null, true); | |
40 types = { '': topType }; | |
41 imports = []; | |
42 natives = []; | |
43 sources = []; | |
44 _privateMembers = {}; | |
45 } | |
46 | |
47 Element get enclosingElement() => null; | |
48 Library get library() => this; | |
49 | |
50 bool get isNative() => topType.isNative; | |
51 | |
52 bool get isCore() => this == world.corelib; | |
53 bool get isCoreImpl() => this == world.coreimpl; | |
54 | |
55 // TODO(jmesserly): we shouldn't be special casing DOM anywhere. | |
56 bool get isDomOrHtml() => this == world.dom || this == world.html; | |
57 | |
58 SourceSpan get span() => new SourceSpan(baseSource, 0, 0); | |
59 | |
60 String makeFullPath(String filename) { | |
61 if (filename.startsWith('dart:')) return filename; | |
62 if (filename.startsWith('package:')) return filename; | |
63 // TODO(jmesserly): replace with node.js path.resolve | |
64 if (filename.startsWith('/')) return filename; | |
65 if (filename.startsWith('file:///')) return filename; | |
66 if (filename.startsWith('http://')) return filename; | |
67 if (const RegExp('^[a-zA-Z]:/').hasMatch(filename)) return filename; | |
68 return joinPaths(sourceDir, filename); | |
69 } | |
70 | |
71 /** Adds an import to the library. */ | |
72 addImport(String fullname, String prefix, SourceSpan span) { | |
73 var newLib = world.getOrAddLibrary(fullname); | |
74 // Special exemption in spec to ensure core is only imported once | |
75 if (newLib.isCore) return; | |
76 imports.add(new LibraryImport(newLib, prefix, span)); | |
77 return newLib; | |
78 } | |
79 | |
80 addNative(String fullname) { | |
81 natives.add(world.reader.readFile(fullname)); | |
82 } | |
83 | |
84 MemberSet _findMembers(String name) { | |
85 if (name.startsWith('_')) { | |
86 return _privateMembers[name]; | |
87 } else { | |
88 return world._members[name]; | |
89 } | |
90 } | |
91 | |
92 _addMember(Member member) { | |
93 if (member.isPrivate) { | |
94 if (member.isStatic) { | |
95 if (member.declaringType.isTop) { | |
96 world._addTopName(member); | |
97 } | |
98 } else { | |
99 var members = _privateMembers[member.name]; | |
100 if (members == null) { | |
101 members = new MemberSet(member, isVar: true); | |
102 _privateMembers[member.name] = members; | |
103 } else { | |
104 members.add(member); | |
105 } | |
106 } | |
107 } else { | |
108 world._addMember(member); | |
109 } | |
110 } | |
111 | |
112 // TODO(jimhug): Cache and share the types as interfaces! | |
113 Type getOrAddFunctionType(Element enclosingElement, String name, | |
114 FunctionDefinition func, MethodData data) { | |
115 // TODO(jimhug): This is redundant now that FunctionDef has type params. | |
116 final def = new FunctionTypeDefinition(func, null, func.span); | |
117 final type = new DefinedType(name, this, def, false); | |
118 type.addMethod(':call', func); | |
119 var m = type.members[':call']; | |
120 m.enclosingElement = enclosingElement; | |
121 m.resolve(); | |
122 m._methodData = data; | |
123 // Function types implement the Function interface. | |
124 type.interfaces = [world.functionType]; | |
125 return type; | |
126 } | |
127 | |
128 /** Adds a type to the library. */ | |
129 DefinedType addType(String name, Node definition, bool isClass) { | |
130 if (types.containsKey(name)) { | |
131 var existingType = types[name]; | |
132 if ((isCore || isCoreImpl) && existingType.definition == null) { | |
133 // TODO(jimhug): Validate compatibility with natives. | |
134 existingType.setDefinition(definition); | |
135 } else { | |
136 world.warning('duplicate definition of $name', definition.span, | |
137 existingType.span); | |
138 } | |
139 } else { | |
140 types[name] = new DefinedType(name, this, definition, isClass); | |
141 } | |
142 | |
143 return types[name]; | |
144 } | |
145 | |
146 Type findType(NameTypeReference type) { | |
147 Type result = findTypeByName(type.name.name); | |
148 if (result == null) return null; | |
149 | |
150 if (type.names != null) { | |
151 if (type.names.length > 1) { | |
152 // TODO(jmesserly): can we ever get legitimate types with more than one | |
153 // name after the library prefix? | |
154 return null; | |
155 } | |
156 | |
157 if (!result.isTop) { | |
158 // No inner type support. If we get first-class types, this should | |
159 // perform a lookup on the type. | |
160 return null; | |
161 } | |
162 | |
163 // The type we got back was the "top level" library type. | |
164 // Now perform a lookup in that library for the next name. | |
165 return result.library.findTypeByName(type.names[0].name); | |
166 } | |
167 return result; | |
168 } | |
169 | |
170 // TODO(jimhug): Should be merged with new lookup method's logic. | |
171 Type findTypeByName(String name) { | |
172 var ret = types[name]; | |
173 | |
174 // Check all imports even if ret != null to detect conflicting names. | |
175 // TODO(jimhug): Only do this on first lookup. | |
176 for (var imported in imports) { | |
177 var newRet = null; | |
178 if (imported.prefix == null) { | |
179 newRet = imported.library.types[name]; | |
180 } else if (imported.prefix == name) { | |
181 newRet = imported.library.topType; | |
182 } | |
183 if (newRet != null) { | |
184 // TODO(jimhug): Should not need ret != newRet here or below. | |
185 if (ret != null && ret != newRet) { | |
186 world.error('conflicting types for "$name"', ret.span, newRet.span); | |
187 } else { | |
188 ret = newRet; | |
189 } | |
190 } | |
191 } | |
192 return ret; | |
193 } | |
194 | |
195 | |
196 // TODO(jimhug): Why is it okay to assume node is NameTypeReference in here? | |
197 Type resolveType(TypeReference node, bool typeErrors, bool allowTypeParams) { | |
198 if (node == null) return world.varType; | |
199 | |
200 var ret = findType(node); | |
201 | |
202 if (ret == null) { | |
203 var message = 'cannot find type ${_getDottedName(node)}'; | |
204 if (typeErrors) { | |
205 world.error(message, node.span); | |
206 return world.objectType; | |
207 } else { | |
208 world.warning(message, node.span); | |
209 return world.varType; | |
210 } | |
211 } | |
212 return ret; | |
213 } | |
214 | |
215 static String _getDottedName(NameTypeReference type) { | |
216 if (type.names != null) { | |
217 var names = map(type.names, (n) => n.name); | |
218 return '${type.name.name}.${Strings.join(names, ".")}'; | |
219 } else { | |
220 return type.name.name; | |
221 } | |
222 } | |
223 | |
224 Member lookup(String name, SourceSpan span) { | |
225 return _topNames[name]; | |
226 } | |
227 | |
228 resolve() { | |
229 if (name == null) { | |
230 // TODO(jimhug): More fodder for io library. | |
231 name = baseSource.filename; | |
232 var index = name.lastIndexOf('/', name.length); | |
233 if (index >= 0) { | |
234 name = name.substring(index+1); | |
235 } | |
236 index = name.indexOf('.'); | |
237 if (index > 0) { | |
238 name = name.substring(0, index); | |
239 } | |
240 } | |
241 // TODO(jimhug): Expand to handle all illegal id characters | |
242 _jsname = | |
243 name.replaceAll('.', '_').replaceAll(':', '_').replaceAll(' ', '_'); | |
244 | |
245 for (var type in types.getValues()) { | |
246 type.resolve(); | |
247 } | |
248 } | |
249 | |
250 _addTopName(String name, Member member, [SourceSpan localSpan]) { | |
251 var existing = _topNames[name]; | |
252 if (existing === null) { | |
253 _topNames[name] = member; | |
254 } else { | |
255 if (existing is AmbiguousMember) { | |
256 existing.members.add(member); | |
257 } else { | |
258 var newMember = new AmbiguousMember(name, [existing, member]); | |
259 world.error('conflicting members for "$name"', | |
260 existing.span, member.span, localSpan); | |
261 _topNames[name] = newMember; | |
262 } | |
263 } | |
264 } | |
265 | |
266 _addTopNames(Library lib) { | |
267 for (var member in lib.topType.members.getValues()) { | |
268 if (member.isPrivate && lib != this) continue; | |
269 _addTopName(member.name, member); | |
270 } | |
271 for (var type in lib.types.getValues()) { | |
272 if (!type.isTop) { | |
273 if (lib != this && type.typeMember.isPrivate) continue; | |
274 _addTopName(type.name, type.typeMember); | |
275 } | |
276 } | |
277 } | |
278 | |
279 /** | |
280 * This method will check for any conflicts in top-level names in this | |
281 * library. It will also build up a map from top-level names to a single | |
282 * member to be used for future lookups both to keep error messages clean | |
283 * and as a minor perf optimization. | |
284 */ | |
285 postResolveChecks() { | |
286 _topNames = {}; | |
287 // check for conflicts between top-level names | |
288 _addTopNames(this); | |
289 for (var imported in imports) { | |
290 if (imported.prefix == null) { | |
291 _addTopNames(imported.library); | |
292 } else { | |
293 _addTopName(imported.prefix, imported.library.topType.typeMember, | |
294 imported.span); | |
295 } | |
296 } | |
297 } | |
298 | |
299 visitSources() { | |
300 var visitor = new _LibraryVisitor(this); | |
301 visitor.addSource(baseSource); | |
302 } | |
303 | |
304 toString() => baseSource.filename; | |
305 | |
306 int hashCode() => baseSource.filename.hashCode(); | |
307 | |
308 bool operator ==(other) => other is Library && | |
309 other.baseSource.filename == baseSource.filename; | |
310 } | |
311 | |
312 | |
313 class _LibraryVisitor implements TreeVisitor { | |
314 final Library library; | |
315 DefinedType currentType; | |
316 List<SourceFile> sources; | |
317 | |
318 bool seenImport = false; | |
319 bool seenSource = false; | |
320 bool seenResource = false; | |
321 bool isTop = true; | |
322 | |
323 _LibraryVisitor(this.library) { | |
324 currentType = library.topType; | |
325 sources = []; | |
326 } | |
327 | |
328 addSourceFromName(String name, SourceSpan span) { | |
329 var filename = library.makeFullPath(name); | |
330 if (filename == library.baseSource.filename) { | |
331 world.error('library cannot source itself', span); | |
332 return; | |
333 } else if (sources.some((s) => s.filename == filename)) { | |
334 world.error('file "$filename" has already been sourced', span); | |
335 return; | |
336 } | |
337 | |
338 var source = world.readFile(library.makeFullPath(name)); | |
339 sources.add(source); | |
340 } | |
341 | |
342 addSource(SourceFile source) { | |
343 if (library.sources.some((s) => s.filename == source.filename)) { | |
344 // TODO(jimhug): good error location. | |
345 world.error('duplicate source file "${source.filename}"', null); | |
346 return; | |
347 } | |
348 library.sources.add(source); | |
349 final parser = new Parser(source, diet: options.dietParse); | |
350 final unit = parser.compilationUnit(); | |
351 | |
352 unit.forEach((def) => def.visit(this)); | |
353 | |
354 assert(sources.length == 0 || isTop); | |
355 isTop = false; | |
356 var newSources = sources; | |
357 sources = []; | |
358 for (var newSource in newSources) { | |
359 addSource(newSource); | |
360 } | |
361 } | |
362 | |
363 void visitDirectiveDefinition(DirectiveDefinition node) { | |
364 if (!isTop) { | |
365 world.error('directives not allowed in sourced file', node.span); | |
366 return; | |
367 } | |
368 | |
369 var name; | |
370 switch (node.name.name) { | |
371 case "library": | |
372 name = getSingleStringArg(node); | |
373 if (library.name == null) { | |
374 library.name = name; | |
375 if (seenImport || seenSource || seenResource) { | |
376 world.error('#library must be first directive in file', node.span); | |
377 } | |
378 } else { | |
379 world.error('already specified library name', node.span); | |
380 } | |
381 break; | |
382 | |
383 case "import": | |
384 seenImport = true; | |
385 name = getFirstStringArg(node); | |
386 var prefix = tryGetNamedStringArg(node, 'prefix'); | |
387 if (node.arguments.length > 2 || | |
388 node.arguments.length == 2 && prefix == null) { | |
389 world.error( | |
390 'expected at most one "name" argument and one optional "prefix"' | |
391 ' but found ${node.arguments.length}', node.span); | |
392 } else if (prefix != null && prefix.indexOf('.') >= 0) { | |
393 world.error('library prefix canot contain "."', node.span); | |
394 } else if (seenSource || seenResource) { | |
395 world.error('#imports must come before any #source or #resource', | |
396 node.span); | |
397 } | |
398 | |
399 // Empty prefix and no prefix are equivalent | |
400 if (prefix == '') prefix = null; | |
401 | |
402 var filename = library.makeFullPath(name); | |
403 | |
404 if (library.imports.some((li) => li.library.baseSource == filename)) { | |
405 // TODO(jimhug): Can you import a lib twice with different prefixes? | |
406 world.error('duplicate import of "$name"', node.span); | |
407 return; | |
408 } | |
409 | |
410 var newLib = library.addImport(filename, prefix, node.span); | |
411 // TODO(jimhug): Add check that imported library has a #library | |
412 break; | |
413 | |
414 case "source": | |
415 seenSource = true; | |
416 name = getSingleStringArg(node); | |
417 addSourceFromName(name, node.span); | |
418 if (seenResource) { | |
419 world.error('#sources must come before any #resource', node.span); | |
420 } | |
421 break; | |
422 | |
423 case "native": | |
424 // TODO(jimhug): Fit this into spec? | |
425 name = getSingleStringArg(node); | |
426 library.addNative(library.makeFullPath(name)); | |
427 break; | |
428 | |
429 case "resource": | |
430 // TODO(jmesserly): should we do anything else here? | |
431 seenResource = true; | |
432 getFirstStringArg(node); | |
433 break; | |
434 | |
435 default: | |
436 world.error('unknown directive: ${node.name.name}', node.span); | |
437 } | |
438 } | |
439 | |
440 String getSingleStringArg(DirectiveDefinition node) { | |
441 if (node.arguments.length != 1) { | |
442 world.error( | |
443 'expected exactly one argument but found ${node.arguments.length}', | |
444 node.span); | |
445 } | |
446 return getFirstStringArg(node); | |
447 } | |
448 | |
449 String getFirstStringArg(DirectiveDefinition node) { | |
450 if (node.arguments.length < 1) { | |
451 world.error( | |
452 'expected at least one argument but found ${node.arguments.length}', | |
453 node.span); | |
454 } | |
455 var arg = node.arguments[0]; | |
456 if (arg.label != null) { | |
457 world.error('label not allowed for directive', node.span); | |
458 } | |
459 return _parseStringArgument(arg); | |
460 } | |
461 | |
462 String tryGetNamedStringArg(DirectiveDefinition node, String argName) { | |
463 var args = node.arguments.filter( | |
464 (a) => a.label != null && a.label.name == argName); | |
465 | |
466 if (args.length == 0) { | |
467 return null; | |
468 } | |
469 if (args.length > 1) { | |
470 world.error('expected at most one "${argName}" argument but found ' | |
471 '${node.arguments.length}', node.span); | |
472 } | |
473 // Even though the collection has one arg, this is the easiest way to get | |
474 // the first item. | |
475 for (var arg in args) { | |
476 return _parseStringArgument(arg); | |
477 } | |
478 } | |
479 | |
480 String _parseStringArgument(ArgumentNode arg) { | |
481 var expr = arg.value; | |
482 if (expr is! LiteralExpression || !expr.value.type.isString) { | |
483 world.error('expected string literal', expr.span); | |
484 } | |
485 return expr.value.actualValue; | |
486 } | |
487 | |
488 void visitTypeDefinition(TypeDefinition node) { | |
489 var oldType = currentType; | |
490 currentType = library.addType(node.name.name, node, node.isClass); | |
491 for (var member in node.body) { | |
492 member.visit(this); | |
493 } | |
494 currentType = oldType; | |
495 } | |
496 | |
497 void visitVariableDefinition(VariableDefinition node) { | |
498 currentType.addField(node); | |
499 } | |
500 | |
501 void visitFunctionDefinition(FunctionDefinition node) { | |
502 currentType.addMethod(node.name.name, node); | |
503 } | |
504 | |
505 void visitFunctionTypeDefinition(FunctionTypeDefinition node) { | |
506 var type = library.addType(node.func.name.name, node, false); | |
507 type.addMethod(':call', node.func); | |
508 } | |
509 } | |
OLD | NEW |