StringRef.h revision c9af366fc39d657b7d416d16988265d86a641184
14cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar//===--- StringRef.h - Constant String Reference Wrapper --------*- C++ -*-===// 24cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar// 34cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar// The LLVM Compiler Infrastructure 44cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar// 54cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar// This file is distributed under the University of Illinois Open Source 64cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar// License. See LICENSE.TXT for details. 74cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar// 84cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar//===----------------------------------------------------------------------===// 94cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 104cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar#ifndef LLVM_ADT_STRINGREF_H 114cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar#define LLVM_ADT_STRINGREF_H 124cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 13d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar#include <algorithm> 1485f49835c288b0107cb4020d4e59e491c146973dDaniel Dunbar#include <cassert> 154cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar#include <cstring> 164cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar#include <string> 174cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 184cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbarnamespace llvm { 19f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar 204cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// StringRef - Represent a constant reference to a string, i.e. a character 214cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// array and a length, which need not be null terminated. 224cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// 234cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// This class does not own the string data, it is expected to be used in 244cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// situations where the character data resides in some other buffer, whose 254cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// lifetime extends past that of the StringRef. For this reason, it is not in 264cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// general safe to store a StringRef. 274cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar class StringRef { 284cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar public: 294cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar typedef const char *iterator; 30b834a7b73ce0dcf8fbf8d8b0d62f69e4b78059adDaniel Dunbar static const size_t npos = ~size_t(0); 314cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 324cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar private: 334cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// The start of the string, in an external buffer. 344cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar const char *Data; 354cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 364cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// The length of the string. 37f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar size_t Length; 384cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 394cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar public: 404cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @name Constructors 414cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @{ 424cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 434cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// Construct an empty string ref. 444cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /*implicit*/ StringRef() : Data(0), Length(0) {} 454cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 464cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// Construct a string ref from a cstring. 474cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /*implicit*/ StringRef(const char *Str) 484cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar : Data(Str), Length(::strlen(Str)) {} 494cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 504cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// Construct a string ref from a pointer and length. 514cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /*implicit*/ StringRef(const char *_Data, unsigned _Length) 524cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar : Data(_Data), Length(_Length) {} 534cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 544cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// Construct a string ref from an std::string. 554cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /*implicit*/ StringRef(const std::string &Str) 564cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar : Data(Str.c_str()), Length(Str.length()) {} 574cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 584cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @} 594cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @name Iterators 604cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @{ 614cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 624cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar iterator begin() const { return Data; } 634cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 644cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar iterator end() const { return Data + Length; } 654cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 664cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @} 674cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @name String Operations 684cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @{ 694cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 704cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// data - Get a pointer to the start of the string (which may not be null 714cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// terminated). 724cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar const char *data() const { return Data; } 734cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 744cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// empty - Check if the string is empty. 754cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar bool empty() const { return Length == 0; } 764cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 774cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// size - Get the string size. 787cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar size_t size() const { return Length; } 797cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar 807cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar /// equals - Check for string equality, this is more efficient than 817cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar /// compare() in when the relative ordering of inequal strings isn't needed. 827cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar bool equals(const StringRef &RHS) const { 837cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar return (Length == RHS.Length && 847e763ebd982e199224a2d2e0cc802d09d2822b34Chris Lattner memcmp(Data, RHS.Data, RHS.Length) == 0); 857cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar } 864cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 874cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// compare - Compare two strings; the result is -1, 0, or 1 if this string 884cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// is lexicographically less than, equal to, or greater than the \arg RHS. 894cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar int compare(const StringRef &RHS) const { 904cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar // Check the prefix for a mismatch. 914cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar if (int Res = memcmp(Data, RHS.Data, std::min(Length, RHS.Length))) 924cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar return Res < 0 ? -1 : 1; 934cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 944cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar // Otherwise the prefixes match, so we only need to check the lengths. 954cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar if (Length == RHS.Length) 964cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar return 0; 974cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar return Length < RHS.Length ? -1 : 1; 984cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar } 994cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 1004cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// str - Get the contents as an std::string. 1014cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar std::string str() const { return std::string(Data, Length); } 1024cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 1034cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @} 1044cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @name Operator Overloads 1054cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @{ 1064cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 1074cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar char operator[](size_t Index) const { 1084cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar assert(Index < Length && "Invalid index!"); 1094cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar return Data[Index]; 1104cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar } 1114cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 1124cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @} 1134cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @name Type Conversions 1144cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @{ 1154cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 1164cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar operator std::string() const { 1174cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar return str(); 1184cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar } 1194cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 1204cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar /// @} 121f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar /// @name Utility Functions 122f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar /// @{ 123f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar 124d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// substr - Return a reference to the substring from [Start, Start + N). 125f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar /// 126f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar /// \param Start - The index of the starting character in the substring; if 127d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// the index is npos or greater than the length of the string then the 128d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// empty substring will be returned. 129f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar /// 130f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar /// \param N - The number of characters to included in the substring. If N 131f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar /// exceeds the number of characters remaining in the string, the string 132f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar /// suffix (starting with \arg Start) will be returned. 133f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar StringRef substr(size_t Start, size_t N = npos) const { 134f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar Start = std::min(Start, Length); 135f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar return StringRef(Data + Start, std::min(N, Length - Start)); 136f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar } 137f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar 138d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// slice - Return a reference to the substring from [Start, End). 139d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// 140d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// \param Start - The index of the starting character in the substring; if 141d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// the index is npos or greater than the length of the string then the 142d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// empty substring will be returned. 143d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// 144d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// \param End - The index following the last character to include in the 145d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// substring. If this is npos, or less than \arg Start, or exceeds the 146d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// number of characters remaining in the string, the string suffix 147d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// (starting with \arg Start) will be returned. 148d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar StringRef slice(size_t Start, size_t End) const { 149d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar Start = std::min(Start, Length); 150d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar End = std::min(std::max(Start, End), Length); 151d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar return StringRef(Data + Start, End - Start); 152d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar } 153d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar 154d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// split - Split into two substrings around the first occurence of a 155d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// separator character. 156d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// 157d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// If \arg Separator is in the string, then the result is a pair (LHS, RHS) 158d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// such that (*this == LHS + Separator + RHS) is true and RHS is 159d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// maximal. If \arg Separator is not in the string, then the result is a 160d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// pair (LHS, RHS) where (*this == LHS) and (RHS == ""). 161d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// 162d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// \param Separator - The character to split on. 163d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar /// \return - The split substrings. 164d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar std::pair<StringRef, StringRef> split(char Separator) const { 165d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar iterator it = std::find(begin(), end(), Separator); 166d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar if (it == end()) 167d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar return std::make_pair(*this, StringRef()); 168d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar return std::make_pair(StringRef(begin(), it - begin()), 169d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar StringRef(it + 1, end() - (it + 1))); 170d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar } 171d61918fc6898a89df8b0a03e068f234ded010cdfDaniel Dunbar 172f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar /// startswith - Check if this string starts with the given \arg Prefix. 173f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar bool startswith(const StringRef &Prefix) const { 1747cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar return substr(0, Prefix.Length).equals(Prefix); 175f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar } 176f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar 177c9af366fc39d657b7d416d16988265d86a641184Daniel Dunbar /// endswith - Check if this string ends with the given \arg Suffix. 178c9af366fc39d657b7d416d16988265d86a641184Daniel Dunbar bool endswith(const StringRef &Suffix) const { 179c9af366fc39d657b7d416d16988265d86a641184Daniel Dunbar return slice(size() - Suffix.Length, size()).equals(Suffix); 180c9af366fc39d657b7d416d16988265d86a641184Daniel Dunbar } 181c9af366fc39d657b7d416d16988265d86a641184Daniel Dunbar 182f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar /// @} 1834cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar }; 184f5fdf73238dfd923f33bcbbd397cff6752d9c41eDaniel Dunbar 1857cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar /// @name StringRef Comparison Operators 1867cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar /// @{ 1877cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar 1887cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar inline bool operator==(const StringRef &LHS, const StringRef &RHS) { 1897cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar return LHS.equals(RHS); 1907cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar } 1917cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar 1927cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar inline bool operator!=(const StringRef &LHS, const StringRef &RHS) { 1937cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar return !(LHS == RHS); 1947cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar } 1957cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar 1967cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar inline bool operator<(const StringRef &LHS, const StringRef &RHS) { 1977cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar return LHS.compare(RHS) == -1; 1987cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar } 1997cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar 2007cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar inline bool operator<=(const StringRef &LHS, const StringRef &RHS) { 2017cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar return LHS.compare(RHS) != 1; 2027cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar } 2037cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar 2047cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar inline bool operator>(const StringRef &LHS, const StringRef &RHS) { 2057cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar return LHS.compare(RHS) == 1; 2067cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar } 2077cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar 2087cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar inline bool operator>=(const StringRef &LHS, const StringRef &RHS) { 2097cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar return LHS.compare(RHS) != -1; 2107cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar } 2117cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar 2127cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar /// @} 2137cb6860185187c74a1205deebff9e0f09a0ddae4Daniel Dunbar 2144cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar} 2154cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar 2164cf95d75c65f37677d306952b0d2306bc6d20b1fDaniel Dunbar#endif 217