HeaderSearch.h revision 0bc735ffcfb223c0186419547abaa5c84482663e
15f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer//===--- HeaderSearch.h - Resolve Header File Locations ---------*- C++ -*-===//
25f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer//
35f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer//                     The LLVM Compiler Infrastructure
45f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer//
50bc735ffcfb223c0186419547abaa5c84482663eChris Lattner// This file is distributed under the University of Illinois Open Source
60bc735ffcfb223c0186419547abaa5c84482663eChris Lattner// License. See LICENSE.TXT for details.
75f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer//
85f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer//===----------------------------------------------------------------------===//
95f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer//
105f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer// This file defines the HeaderSearch interface.
115f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer//
125f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer//===----------------------------------------------------------------------===//
135f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
145f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer#ifndef LLVM_CLANG_LEX_HEADERSEARCH_H
155f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer#define LLVM_CLANG_LEX_HEADERSEARCH_H
165f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
175f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer#include "clang/Lex/DirectoryLookup.h"
185f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer#include "llvm/ADT/StringMap.h"
195f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer#include <vector>
205f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
215f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencernamespace clang {
225f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerclass FileEntry;
235f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerclass FileManager;
245f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerclass IdentifierInfo;
255f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
265f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
275f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer/// HeaderSearch - This class encapsulates the information needed to find the
285f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer/// file referenced by a #include or #include_next, (sub-)framework lookup, etc.
295f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerclass HeaderSearch {
305f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  FileManager &FileMgr;
315f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
325f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// #include search path information.  Requests for #include "x" search the
335f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// directory of the #including file first, then each directory in SearchDirs
345f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// consequtively. Requests for <x> search the current dir first, then each
355f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// directory in SearchDirs, starting at SystemDirIdx, consequtively.  If
365f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// NoCurDirSearch is true, then the check for the file in the current
375f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// directory is supressed.
385f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  std::vector<DirectoryLookup> SearchDirs;
395f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  unsigned SystemDirIdx;
405f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  bool NoCurDirSearch;
415f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
425f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// PreFileInfo - The preprocessor keeps track of this information for each
435f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// file that is #included.
445f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  struct PerFileInfo {
455f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /// isImport - True if this is a #import'd or #pragma once file.
465f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    bool isImport : 1;
475f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
485f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /// DirInfo - Keep track of whether this is a system header, and if so,
495f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /// whether it is C++ clean or not.  This can be set by the include paths or
505f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /// by #pragma gcc system_header.
515f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    DirectoryLookup::DirType DirInfo : 2;
525f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
535f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /// NumIncludes - This is the number of times the file has been included
545f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /// already.
555f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    unsigned short NumIncludes;
565f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
575f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /// ControllingMacro - If this file has a #ifndef XXX (or equivalent) guard
585f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /// that protects the entire contents of the file, this is the identifier
595f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /// for the macro that controls whether or not it has any effect.
605f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    const IdentifierInfo *ControllingMacro;
615f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
625f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    PerFileInfo() : isImport(false), DirInfo(DirectoryLookup::NormalHeaderDir),
635f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer      NumIncludes(0), ControllingMacro(0) {}
645f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  };
655f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
665f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// FileInfo - This contains all of the preprocessor-specific data about files
675f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// that are included.  The vector is indexed by the FileEntry's UID.
685f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  ///
695f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  std::vector<PerFileInfo> FileInfo;
705f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
719960ae8ecfa2c4278dac708a02e463f83fdf17e8Chris Lattner  /// LookupFileCache - This is keeps track of each lookup performed by
729960ae8ecfa2c4278dac708a02e463f83fdf17e8Chris Lattner  /// LookupFile.  The first part of the value is the starting index in
739960ae8ecfa2c4278dac708a02e463f83fdf17e8Chris Lattner  /// SearchDirs that the cached search was performed from.  If there is a hit
749960ae8ecfa2c4278dac708a02e463f83fdf17e8Chris Lattner  /// and this value doesn't match the current query, the cache has to be
759960ae8ecfa2c4278dac708a02e463f83fdf17e8Chris Lattner  /// ignored.  The second value is the entry in SearchDirs that satisfied the
769960ae8ecfa2c4278dac708a02e463f83fdf17e8Chris Lattner  /// query.
779960ae8ecfa2c4278dac708a02e463f83fdf17e8Chris Lattner  llvm::StringMap<std::pair<unsigned, unsigned> > LookupFileCache;
789960ae8ecfa2c4278dac708a02e463f83fdf17e8Chris Lattner
799960ae8ecfa2c4278dac708a02e463f83fdf17e8Chris Lattner
805f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// FrameworkMap - This is a collection mapping a framework or subframework
815f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// name like "Carbon" to the Carbon.framework directory.
825f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  llvm::StringMap<const DirectoryEntry *> FrameworkMap;
835f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
84822da61b74ce14e89b3fa8774db18c833aa5748bChris Lattner  /// HeaderMaps - This is a mapping from FileEntry -> HeaderMap, uniquing
85822da61b74ce14e89b3fa8774db18c833aa5748bChris Lattner  /// headermaps.  This vector owns the headermap.
86822da61b74ce14e89b3fa8774db18c833aa5748bChris Lattner  std::vector<std::pair<const FileEntry*, const HeaderMap*> > HeaderMaps;
87822da61b74ce14e89b3fa8774db18c833aa5748bChris Lattner
885f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  // Various statistics we track for performance analysis.
895f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  unsigned NumIncluded;
905f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  unsigned NumMultiIncludeFileOptzn;
915f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  unsigned NumFrameworkLookups, NumSubFrameworkLookups;
925f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerpublic:
935f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  HeaderSearch(FileManager &FM);
94822da61b74ce14e89b3fa8774db18c833aa5748bChris Lattner  ~HeaderSearch();
955f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
965f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  FileManager &getFileMgr() const { return FileMgr; }
975f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
985f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// SetSearchPaths - Interface for setting the file search paths.
995f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  ///
1005f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  void SetSearchPaths(const std::vector<DirectoryLookup> &dirs,
1015f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                      unsigned systemDirIdx, bool noCurDirSearch) {
1025f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    SearchDirs = dirs;
1035f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    SystemDirIdx = systemDirIdx;
1045f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    NoCurDirSearch = noCurDirSearch;
1059960ae8ecfa2c4278dac708a02e463f83fdf17e8Chris Lattner    //LookupFileCache.clear();
1065f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  }
1075f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1085f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// ClearFileInfo - Forget everything we know about headers so far.
1095f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  void ClearFileInfo() {
1105f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    FileInfo.clear();
1115f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  }
1125f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1135f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// LookupFile - Given a "foo" or <foo> reference, look up the indicated file,
1145f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// return null on failure.  isAngled indicates whether the file reference is
1155f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// a <> reference.  If successful, this returns 'UsedDir', the
1165f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// DirectoryLookup member the file was found in, or null if not applicable.
1175f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// If CurDir is non-null, the file was found in the specified directory
1185f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// search location.  This is used to implement #include_next.  CurFileEnt, if
1195f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// non-null, indicates where the #including file is, in case a relative
1205f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// search is needed.
1215f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  const FileEntry *LookupFile(const char *FilenameStart,
1225f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                              const char *FilenameEnd, bool isAngled,
1235f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                              const DirectoryLookup *FromDir,
1245f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                              const DirectoryLookup *&CurDir,
1255f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                              const FileEntry *CurFileEnt);
1265f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1275f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// LookupSubframeworkHeader - Look up a subframework for the specified
1285f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// #include file.  For example, if #include'ing <HIToolbox/HIToolbox.h> from
1295f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// within ".../Carbon.framework/Headers/Carbon.h", check to see if HIToolbox
1305f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// is a subframework within Carbon.framework.  If so, return the FileEntry
1315f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// for the designated file, otherwise return null.
1325f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  const FileEntry *LookupSubframeworkHeader(const char *FilenameStart,
1335f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                                            const char *FilenameEnd,
1345f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                                            const FileEntry *RelativeFileEnt);
1355f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
136afded5bbb85607023c710c3d6a96c372feb84d7fChris Lattner  /// LookupFrameworkCache - Look up the specified framework name in our
137afded5bbb85607023c710c3d6a96c372feb84d7fChris Lattner  /// framework cache, returning the DirectoryEntry it is in if we know,
138afded5bbb85607023c710c3d6a96c372feb84d7fChris Lattner  /// otherwise, return null.
139afded5bbb85607023c710c3d6a96c372feb84d7fChris Lattner  const DirectoryEntry *&LookupFrameworkCache(const char *FWNameStart,
140afded5bbb85607023c710c3d6a96c372feb84d7fChris Lattner                                              const char *FWNameEnd) {
141afded5bbb85607023c710c3d6a96c372feb84d7fChris Lattner    return FrameworkMap.GetOrCreateValue(FWNameStart, FWNameEnd).getValue();
142afded5bbb85607023c710c3d6a96c372feb84d7fChris Lattner  }
143afded5bbb85607023c710c3d6a96c372feb84d7fChris Lattner
1445f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// ShouldEnterIncludeFile - Mark the specified file as a target of of a
1455f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// #include, #include_next, or #import directive.  Return false if #including
1465f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// the file will have no effect or true if we should include it.
1475f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  bool ShouldEnterIncludeFile(const FileEntry *File, bool isImport);
1485f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1495f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1505f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// getFileDirFlavor - Return whether the specified file is a normal header,
1515f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// a system header, or a C++ friendly system header.
1525f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  DirectoryLookup::DirType getFileDirFlavor(const FileEntry *File) {
1535f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    return getFileInfo(File).DirInfo;
1545f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  }
1555f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1565f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// MarkFileIncludeOnce - Mark the specified file as a "once only" file, e.g.
1575f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// due to #pragma once.
1585f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  void MarkFileIncludeOnce(const FileEntry *File) {
1595f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    getFileInfo(File).isImport = true;
1605f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  }
1615f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1625f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// MarkFileSystemHeader - Mark the specified fiel as a system header, e.g.
1635f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// due to #pragma GCC system_header.
1645f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  void MarkFileSystemHeader(const FileEntry *File) {
1655f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    getFileInfo(File).DirInfo = DirectoryLookup::SystemHeaderDir;
1665f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  }
1675f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
16825bfcb927d9169ea675ce6e98d8992efceeb0e42Chris Lattner  /// IncrementIncludeCount - Increment the count for the number of times the
16925bfcb927d9169ea675ce6e98d8992efceeb0e42Chris Lattner  /// specified FileEntry has been entered.
17025bfcb927d9169ea675ce6e98d8992efceeb0e42Chris Lattner  void IncrementIncludeCount(const FileEntry *File) {
17125bfcb927d9169ea675ce6e98d8992efceeb0e42Chris Lattner    ++getFileInfo(File).NumIncludes;
17225bfcb927d9169ea675ce6e98d8992efceeb0e42Chris Lattner  }
17325bfcb927d9169ea675ce6e98d8992efceeb0e42Chris Lattner
1745f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// SetFileControllingMacro - Mark the specified file as having a controlling
1755f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// macro.  This is used by the multiple-include optimization to eliminate
1765f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// no-op #includes.
1775f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  void SetFileControllingMacro(const FileEntry *File,
1785f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                               const IdentifierInfo *ControllingMacro) {
1795f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    getFileInfo(File).ControllingMacro = ControllingMacro;
1805f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  }
1815f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
182822da61b74ce14e89b3fa8774db18c833aa5748bChris Lattner  /// CreateHeaderMap - This method returns a HeaderMap for the specified
183822da61b74ce14e89b3fa8774db18c833aa5748bChris Lattner  /// FileEntry, uniquing them through the the 'HeaderMaps' datastructure.
1841bfd4a6313ea8ebf710c46c10111732cc65d51f6Chris Lattner  const HeaderMap *CreateHeaderMap(const FileEntry *FE);
185822da61b74ce14e89b3fa8774db18c833aa5748bChris Lattner
186afded5bbb85607023c710c3d6a96c372feb84d7fChris Lattner  void IncrementFrameworkLookupCount() { ++NumFrameworkLookups; }
187afded5bbb85607023c710c3d6a96c372feb84d7fChris Lattner
1885f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  void PrintStats();
1895f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerprivate:
1905f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1915f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// getFileInfo - Return the PerFileInfo structure for the specified
1925f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  /// FileEntry.
1935f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer  PerFileInfo &getFileInfo(const FileEntry *FE);
1945f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer};
1955f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1965f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer}  // end namespace clang
1975f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1985f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer#endif
199