summaryrefslogtreecommitdiffstats
path: root/source/core/slang-io.h
diff options
context:
space:
mode:
authorEllie Hermaszewska <ellieh@nvidia.com>2024-10-29 14:49:26 +0800
committerGitHub <noreply@github.com>2024-10-29 14:49:26 +0800
commitf65d756bff8d4c5cbc15bd0322a2ae8e6b896a21 (patch)
treeea1d61342cd29368e19135000ec2948813096205 /source/core/slang-io.h
parenta729c15e9dce9f5116a38afc66329ab2ca4cea54 (diff)
format
* format * Minor test fixes * enable checking cpp format in ci
Diffstat (limited to 'source/core/slang-io.h')
-rw-r--r--source/core/slang-io.h548
1 files changed, 286 insertions, 262 deletions
diff --git a/source/core/slang-io.h b/source/core/slang-io.h
index 676635a36..73dfd31d7 100644
--- a/source/core/slang-io.h
+++ b/source/core/slang-io.h
@@ -1,326 +1,350 @@
#ifndef SLANG_CORE_IO_H
#define SLANG_CORE_IO_H
-#include "slang-string.h"
+#include "slang-blob.h"
+#include "slang-secure-crt.h"
#include "slang-stream.h"
+#include "slang-string.h"
#include "slang-text-io.h"
-#include "slang-secure-crt.h"
-#include "slang-blob.h"
namespace Slang
{
- class File
- {
- public:
- static bool exists(const String& fileName);
+class File
+{
+public:
+ static bool exists(const String& fileName);
- static SlangResult readAllText(const String& fileName, String& outString);
+ static SlangResult readAllText(const String& fileName, String& outString);
- static SlangResult readAllBytes(const String& fileName, List<unsigned char>& out);
- static SlangResult readAllBytes(const String& fileName, ScopedAllocation& out);
+ static SlangResult readAllBytes(const String& fileName, List<unsigned char>& out);
+ static SlangResult readAllBytes(const String& fileName, ScopedAllocation& out);
- static SlangResult writeAllText(const String& fileName, const String& text);
+ static SlangResult writeAllText(const String& fileName, const String& text);
- static SlangResult writeAllTextIfChanged(const String& fileName, UnownedStringSlice text);
+ static SlangResult writeAllTextIfChanged(const String& fileName, UnownedStringSlice text);
- /// Write as text in native form for the target (so typically may change line endings )
- static SlangResult writeNativeText(const String& filename, const void* data, size_t size);
+ /// Write as text in native form for the target (so typically may change line endings )
+ static SlangResult writeNativeText(const String& filename, const void* data, size_t size);
- static SlangResult writeAllBytes(const String& fileName, const void* data, size_t size);
+ static SlangResult writeAllBytes(const String& fileName, const void* data, size_t size);
- static SlangResult remove(const String& fileName);
+ static SlangResult remove(const String& fileName);
- static SlangResult makeExecutable(const String& fileName);
+ static SlangResult makeExecutable(const String& fileName);
- /// Creates a temporary file typically in some way based on the prefix
- /// The file will be *created* with the outFileName, on success.
- /// It's creation in necessary to lock that particular name.
- static SlangResult generateTemporary(const UnownedStringSlice& prefix, String& outFileName);
- };
+ /// Creates a temporary file typically in some way based on the prefix
+ /// The file will be *created* with the outFileName, on success.
+ /// It's creation in necessary to lock that particular name.
+ static SlangResult generateTemporary(const UnownedStringSlice& prefix, String& outFileName);
+};
- class Path
- {
- public:
- typedef uint32_t SimplifyIntegral;
+class Path
+{
+public:
+ typedef uint32_t SimplifyIntegral;
- struct SimplifyFlag
+ struct SimplifyFlag
+ {
+ enum Enum : SimplifyIntegral
{
- enum Enum : SimplifyIntegral
- {
- /// Can only simplify to an absolute path. Will return an error if not possible.
- /// Useful to constrain a path, such as when wanting something like 'chroot'.
- AbsoluteOnly = 0x1,
- /// If the simplified path is a root path, remove the root.
- /// Will mean that for example
- /// "/" -> "."
- /// "/a/.." -> "."
- /// "/a" -> "a"
- /// Its worth noting that a path prefixed "/" will never be returned and if *just* the root it specified
- /// it will return as ".".
- NoRoot = 0x2,
- };
+ /// Can only simplify to an absolute path. Will return an error if not possible.
+ /// Useful to constrain a path, such as when wanting something like 'chroot'.
+ AbsoluteOnly = 0x1,
+ /// If the simplified path is a root path, remove the root.
+ /// Will mean that for example
+ /// "/" -> "."
+ /// "/a/.." -> "."
+ /// "/a" -> "a"
+ /// Its worth noting that a path prefixed "/" will never be returned and if *just* the
+ /// root it specified it will return as ".".
+ NoRoot = 0x2,
};
+ };
- // A more convenient typesafe way to specify the SimplifyFlag combinations
- enum SimplifyStyle : SimplifyIntegral
- {
- Normal = 0,
- AbsoluteOnly = SimplifyFlag::AbsoluteOnly,
- NoRoot = SimplifyFlag::NoRoot,
- AbsoluteOnlyAndNoRoot = SimplifyFlag::AbsoluteOnly | SimplifyFlag::NoRoot,
- };
+ // A more convenient typesafe way to specify the SimplifyFlag combinations
+ enum SimplifyStyle : SimplifyIntegral
+ {
+ Normal = 0,
+ AbsoluteOnly = SimplifyFlag::AbsoluteOnly,
+ NoRoot = SimplifyFlag::NoRoot,
+ AbsoluteOnlyAndNoRoot = SimplifyFlag::AbsoluteOnly | SimplifyFlag::NoRoot,
+ };
- enum class Type
- {
- Unknown,
- File,
- Directory,
- };
+ enum class Type
+ {
+ Unknown,
+ File,
+ Directory,
+ };
- typedef uint32_t TypeFlags;
- struct TypeFlag
+ typedef uint32_t TypeFlags;
+ struct TypeFlag
+ {
+ enum Enum : TypeFlags
{
- enum Enum : TypeFlags
- {
- Unknown = TypeFlags(1) << int(Type::Unknown),
- File = TypeFlags(1) << int(Type::File),
- Directory = TypeFlags(1) << int(Type::Directory),
- };
+ Unknown = TypeFlags(1) << int(Type::Unknown),
+ File = TypeFlags(1) << int(Type::File),
+ Directory = TypeFlags(1) << int(Type::Directory),
};
+ };
- class Visitor
- {
- public:
- virtual void accept(Type type, const UnownedStringSlice& filename) = 0;
- };
+ class Visitor
+ {
+ public:
+ virtual void accept(Type type, const UnownedStringSlice& filename) = 0;
+ };
- static const char kPathDelimiter = '/';
+ static const char kPathDelimiter = '/';
#if SLANG_WINDOWS_FAMILY
- static const char kOSCanonicalPathDelimiter = '\\';
- static const char kOSAlternativePathDelimiter = '/';
+ static const char kOSCanonicalPathDelimiter = '\\';
+ static const char kOSAlternativePathDelimiter = '/';
#else
- static const char kOSCanonicalPathDelimiter = '/';
- static const char kOSAlternativePathDelimiter = '/';
+ static const char kOSCanonicalPathDelimiter = '/';
+ static const char kOSAlternativePathDelimiter = '/';
#endif
- /// Finds all all the items in the specified directory, that matches the pattern.
- ///
- /// @param directoryPath The directory to do the search in. If the directory is not found, SLANG_E_NOT_FOUND is returned
- /// @param pattern. The pattern to match against. The pattern matching is targtet specific (ie window matching is different to linux/unix). Passing nullptr means no matching.
- /// @return is SLANG_E_NOT_FOUND if the directoryPath is not found
- static SlangResult find(const String& directoryPath, const char* pattern, Visitor* visitor);
-
- /// Returns -1 if no separator is found
- static Index findLastSeparatorIndex(String const& path) { return findLastSeparatorIndex(path.getUnownedSlice()); }
- static Index findLastSeparatorIndex(UnownedStringSlice const& path);
- /// Finds the index of the last dot in a path, else returns -1
- static Index findExtIndex(String const& path) { return findExtIndex(path.getUnownedSlice()); }
- static Index findExtIndex(UnownedStringSlice const& path);
-
- /// True if isn't just a name (ie has any path separator)
- /// Note this is no the same as having a 'parent' as '/thing' 'has a path', but it doesn't have a parent.
- static bool hasPath(const UnownedStringSlice& path) { return findLastSeparatorIndex(path) >= 0; }
- static bool hasPath(const String& path) { return findLastSeparatorIndex(path) >= 0; }
-
- static String replaceExt(const String& path, const char* newExt);
- static String getFileName(const String& path);
- static String getPathWithoutExt(const String& path);
-
- static String getPathExt(const String& path) { return getPathExt(path.getUnownedSlice()); }
- static UnownedStringSlice getPathExt(const UnownedStringSlice& path);
-
- static String getParentDirectory(const String& path);
-
- static String getFileNameWithoutExt(const String& path);
-
- static String combine(const String& path1, const String& path2);
- static String combine(const String& path1, const String& path2, const String& path3);
-
- /// Combine path sections and store the result in outBuilder
- static void combineIntoBuilder(const UnownedStringSlice& path1, const UnownedStringSlice& path2, StringBuilder& outBuilder);
-
- /// Append a path, taking into account path separators onto the end of ioBuilder
- static void append(StringBuilder& ioBuilder, const UnownedStringSlice& path);
-
- static bool createDirectory(const String& path);
- static bool createDirectoryRecursive(const String& path);
-
- /// Accept either style of delimiter
- SLANG_FORCE_INLINE static bool isDelimiter(char c) { return c == '/' || c == '\\'; }
-
- /// True if the element appears to be a drive specification (where element is the prefix to a path that isn't a directory)
- /// @param pathPrefix The path prefix to test if it's a drive specification
- static bool isDriveSpecification(const UnownedStringSlice& pathPrefix);
-
- /// Splits the path into it's individual bits
- /// Absolute paths of the form "/" will become [""]
- /// Absolute paths of the form "a:/" will become ["a:", ""]
- /// A drive specification of the form "a:" will become ["a:"]
- /// Relative paths that are in effect "." will become []
- static void split(const UnownedStringSlice& path, List<UnownedStringSlice>& splitOut);
-
- /// Strips .. and . as much as it can
- static String simplify(const UnownedStringSlice& path);
- static String simplify(const String& path) { return simplify(path.getUnownedSlice()); }
-
- /// Given a path simplifies it such the the resultant path is absolute (ie contains no . or ..)
- /// Same behavior as simplify around the root
- static SlangResult simplify(const UnownedStringSlice& path, SimplifyStyle style, StringBuilder& outPath);
- static SlangResult simplify(const String& path, SimplifyStyle style, StringBuilder& outPath) { return simplify(path.getUnownedSlice(), style, outPath); }
- static SlangResult simplify(const char* path, SimplifyStyle style, StringBuilder& outPath) { return simplify(UnownedStringSlice(path), style, outPath); }
-
- /// Simplifies the path split up
- static void simplify(List<UnownedStringSlice>& ioSplit);
-
- /// Join the parts of the path to produce an output path
- static void join(const UnownedStringSlice* slices, Index count, StringBuilder& out);
-
- /// Returns true if the path is absolute
- static bool isAbsolute(const UnownedStringSlice& path);
- static bool isAbsolute(const String& path) { return isAbsolute(path.getUnownedSlice()); }
-
- /// Returns true if path contains contains an element of . or ..
- static bool hasRelativeElement(const UnownedStringSlice& path);
- static bool hasRelativeElement(const String& path) { return hasRelativeElement(path.getUnownedSlice()); }
-
- /// Determines the type of file at the path
- /// @param path The path to test
- /// @param outPathType Holds the object type at the path on success
- /// @return SLANG_OK on success
- static SlangResult getPathType(const String& path, SlangPathType* outPathType);
-
- /// Determines the canonical equivalent path to path.
- /// The path returned should reference the identical object - and two different references to the same path should return the same canonical path
- /// @param path Path to get the canonical path for
- /// @param outCanonicalPath The canonical path for 'path' is call is successful
- /// @return SLANG_OK on success
- static SlangResult getCanonical(const String& path, String& outCanonicalPath);
-
- /// Returns the current working directory
- /// @return The path in platform native format. Returns empty string if failed.
- static String getCurrentPath();
-
- /// Returns the executable path
- /// @return The path in platform native format. Returns empty string if failed.
- static String getExecutablePath();
-
- /// Returns the first element of the path or an empty slice if there is none
- /// This broadly equivalent to returning the first element of split
- /// @param path Path to extract first element from
- /// @return The first element of the path, or empty
- static UnownedStringSlice getFirstElement(const UnownedStringSlice& path);
-
- /// Remove a file or directory at specified path. The directory must be empty for it to be removed
- /// @param path
- /// @return SLANG_OK if file or directory is removed
- static SlangResult remove(const String& path);
+ /// Finds all all the items in the specified directory, that matches the pattern.
+ ///
+ /// @param directoryPath The directory to do the search in. If the directory is not found,
+ /// SLANG_E_NOT_FOUND is returned
+ /// @param pattern. The pattern to match against. The pattern matching is targtet specific (ie
+ /// window matching is different to linux/unix). Passing nullptr means no matching.
+ /// @return is SLANG_E_NOT_FOUND if the directoryPath is not found
+ static SlangResult find(const String& directoryPath, const char* pattern, Visitor* visitor);
+
+ /// Returns -1 if no separator is found
+ static Index findLastSeparatorIndex(String const& path)
+ {
+ return findLastSeparatorIndex(path.getUnownedSlice());
+ }
+ static Index findLastSeparatorIndex(UnownedStringSlice const& path);
+ /// Finds the index of the last dot in a path, else returns -1
+ static Index findExtIndex(String const& path) { return findExtIndex(path.getUnownedSlice()); }
+ static Index findExtIndex(UnownedStringSlice const& path);
+
+ /// True if isn't just a name (ie has any path separator)
+ /// Note this is no the same as having a 'parent' as '/thing' 'has a path', but it doesn't have
+ /// a parent.
+ static bool hasPath(const UnownedStringSlice& path)
+ {
+ return findLastSeparatorIndex(path) >= 0;
+ }
+ static bool hasPath(const String& path) { return findLastSeparatorIndex(path) >= 0; }
+
+ static String replaceExt(const String& path, const char* newExt);
+ static String getFileName(const String& path);
+ static String getPathWithoutExt(const String& path);
+
+ static String getPathExt(const String& path) { return getPathExt(path.getUnownedSlice()); }
+ static UnownedStringSlice getPathExt(const UnownedStringSlice& path);
+
+ static String getParentDirectory(const String& path);
+
+ static String getFileNameWithoutExt(const String& path);
+
+ static String combine(const String& path1, const String& path2);
+ static String combine(const String& path1, const String& path2, const String& path3);
+
+ /// Combine path sections and store the result in outBuilder
+ static void combineIntoBuilder(
+ const UnownedStringSlice& path1,
+ const UnownedStringSlice& path2,
+ StringBuilder& outBuilder);
+
+ /// Append a path, taking into account path separators onto the end of ioBuilder
+ static void append(StringBuilder& ioBuilder, const UnownedStringSlice& path);
+
+ static bool createDirectory(const String& path);
+ static bool createDirectoryRecursive(const String& path);
+
+ /// Accept either style of delimiter
+ SLANG_FORCE_INLINE static bool isDelimiter(char c) { return c == '/' || c == '\\'; }
+
+ /// True if the element appears to be a drive specification (where element is the prefix to a
+ /// path that isn't a directory)
+ /// @param pathPrefix The path prefix to test if it's a drive specification
+ static bool isDriveSpecification(const UnownedStringSlice& pathPrefix);
+
+ /// Splits the path into it's individual bits
+ /// Absolute paths of the form "/" will become [""]
+ /// Absolute paths of the form "a:/" will become ["a:", ""]
+ /// A drive specification of the form "a:" will become ["a:"]
+ /// Relative paths that are in effect "." will become []
+ static void split(const UnownedStringSlice& path, List<UnownedStringSlice>& splitOut);
+
+ /// Strips .. and . as much as it can
+ static String simplify(const UnownedStringSlice& path);
+ static String simplify(const String& path) { return simplify(path.getUnownedSlice()); }
+
+ /// Given a path simplifies it such the the resultant path is absolute (ie contains no . or ..)
+ /// Same behavior as simplify around the root
+ static SlangResult simplify(
+ const UnownedStringSlice& path,
+ SimplifyStyle style,
+ StringBuilder& outPath);
+ static SlangResult simplify(const String& path, SimplifyStyle style, StringBuilder& outPath)
+ {
+ return simplify(path.getUnownedSlice(), style, outPath);
+ }
+ static SlangResult simplify(const char* path, SimplifyStyle style, StringBuilder& outPath)
+ {
+ return simplify(UnownedStringSlice(path), style, outPath);
+ }
- /// Remove a file or directory at specified path. The directory can be non-empty.
- /// @param path
- /// @return SLANG_OK if file or directory is removed
- static SlangResult removeNonEmpty(const String& path);
+ /// Simplifies the path split up
+ static void simplify(List<UnownedStringSlice>& ioSplit);
- static bool equals(String path1, String path2);
+ /// Join the parts of the path to produce an output path
+ static void join(const UnownedStringSlice* slices, Index count, StringBuilder& out);
- /// Turn `path` into a relative path from base.
- static String getRelativePath(String base, String path);
- };
+ /// Returns true if the path is absolute
+ static bool isAbsolute(const UnownedStringSlice& path);
+ static bool isAbsolute(const String& path) { return isAbsolute(path.getUnownedSlice()); }
- struct URI
+ /// Returns true if path contains contains an element of . or ..
+ static bool hasRelativeElement(const UnownedStringSlice& path);
+ static bool hasRelativeElement(const String& path)
{
- String uri;
- bool operator==(const URI& other) const { return uri == other.uri; }
- bool operator!=(const URI& other) const { return uri != other.uri; }
+ return hasRelativeElement(path.getUnownedSlice());
+ }
+
+ /// Determines the type of file at the path
+ /// @param path The path to test
+ /// @param outPathType Holds the object type at the path on success
+ /// @return SLANG_OK on success
+ static SlangResult getPathType(const String& path, SlangPathType* outPathType);
+
+ /// Determines the canonical equivalent path to path.
+ /// The path returned should reference the identical object - and two different references to
+ /// the same path should return the same canonical path
+ /// @param path Path to get the canonical path for
+ /// @param outCanonicalPath The canonical path for 'path' is call is successful
+ /// @return SLANG_OK on success
+ static SlangResult getCanonical(const String& path, String& outCanonicalPath);
+
+ /// Returns the current working directory
+ /// @return The path in platform native format. Returns empty string if failed.
+ static String getCurrentPath();
+
+ /// Returns the executable path
+ /// @return The path in platform native format. Returns empty string if failed.
+ static String getExecutablePath();
+
+ /// Returns the first element of the path or an empty slice if there is none
+ /// This broadly equivalent to returning the first element of split
+ /// @param path Path to extract first element from
+ /// @return The first element of the path, or empty
+ static UnownedStringSlice getFirstElement(const UnownedStringSlice& path);
+
+ /// Remove a file or directory at specified path. The directory must be empty for it to be
+ /// removed
+ /// @param path
+ /// @return SLANG_OK if file or directory is removed
+ static SlangResult remove(const String& path);
+
+ /// Remove a file or directory at specified path. The directory can be non-empty.
+ /// @param path
+ /// @return SLANG_OK if file or directory is removed
+ static SlangResult removeNonEmpty(const String& path);
+
+ static bool equals(String path1, String path2);
+
+ /// Turn `path` into a relative path from base.
+ static String getRelativePath(String base, String path);
+};
+
+struct URI
+{
+ String uri;
+ bool operator==(const URI& other) const { return uri == other.uri; }
+ bool operator!=(const URI& other) const { return uri != other.uri; }
- HashCode getHashCode() const { return uri.getHashCode(); }
+ HashCode getHashCode() const { return uri.getHashCode(); }
- bool isLocalFile() { return uri.startsWith("file://"); };
- String getPath() const;
- StringSlice getProtocol() const;
+ bool isLocalFile() { return uri.startsWith("file://"); };
+ String getPath() const;
+ StringSlice getProtocol() const;
- static URI fromLocalFilePath(UnownedStringSlice path);
- static URI fromString(UnownedStringSlice uriString);
- static bool isSafeURIChar(char ch);
- };
+ static URI fromLocalFilePath(UnownedStringSlice path);
+ static URI fromString(UnownedStringSlice uriString);
+ static bool isSafeURIChar(char ch);
+};
- /// Helper class abstracting lock files.
- /// Uses LockFileEx() on windows systems and flock() on POSIX systems.
- class LockFile
+/// Helper class abstracting lock files.
+/// Uses LockFileEx() on windows systems and flock() on POSIX systems.
+class LockFile
+{
+public:
+ enum class LockType
{
- public:
- enum class LockType
- {
- Exclusive,
- Shared,
- };
+ Exclusive,
+ Shared,
+ };
- /// Open the lock file. This will create the file if it doesn't exist yet.
- /// @param fileName File name to open.
- /// @return SLANG_OK on success.
- SlangResult open(const String& fileName);
+ /// Open the lock file. This will create the file if it doesn't exist yet.
+ /// @param fileName File name to open.
+ /// @return SLANG_OK on success.
+ SlangResult open(const String& fileName);
- /// Closes the lock file.
- void close();
+ /// Closes the lock file.
+ void close();
- /// Returns true if the lock file is open.
- bool isOpen() const { return m_isOpen; }
+ /// Returns true if the lock file is open.
+ bool isOpen() const { return m_isOpen; }
- /// Acquire the lock in non-blocking mode.
- /// @param lockType Lock type (Exclusive or Shared).
- /// @return SLANG_OK on success. SLANG_E_TIME_OUT if the lock is already held.
- SlangResult tryLock(LockType lockType = LockType::Exclusive);
+ /// Acquire the lock in non-blocking mode.
+ /// @param lockType Lock type (Exclusive or Shared).
+ /// @return SLANG_OK on success. SLANG_E_TIME_OUT if the lock is already held.
+ SlangResult tryLock(LockType lockType = LockType::Exclusive);
- /// Acquire the lock in blocking mode.
- /// @param lockType Lock type (Exclusive or Shared).
- /// @return SLANG_OK on success.
- SlangResult lock(LockType lockType = LockType::Exclusive);
+ /// Acquire the lock in blocking mode.
+ /// @param lockType Lock type (Exclusive or Shared).
+ /// @return SLANG_OK on success.
+ SlangResult lock(LockType lockType = LockType::Exclusive);
- /// Release the lock.
- /// @return SLANG_OK on success.
- SlangResult unlock();
+ /// Release the lock.
+ /// @return SLANG_OK on success.
+ SlangResult unlock();
- LockFile();
- ~LockFile();
+ LockFile();
+ ~LockFile();
- private:
- LockFile(const LockFile&) = delete;
- LockFile(LockFile&&) = delete;
- LockFile& operator=(const LockFile&) = delete;
- LockFile& operator=(LockFile&&) = delete;
+private:
+ LockFile(const LockFile&) = delete;
+ LockFile(LockFile&&) = delete;
+ LockFile& operator=(const LockFile&) = delete;
+ LockFile& operator=(LockFile&&) = delete;
#if SLANG_WINDOWS_FAMILY
- void* m_fileHandle;
+ void* m_fileHandle;
#else
- int m_fileHandle;
+ int m_fileHandle;
#endif
- bool m_isOpen;
- };
+ bool m_isOpen;
+};
- class LockFileGuard
+class LockFileGuard
+{
+public:
+ LockFileGuard(LockFile& lockFile, LockFile::LockType lockType = LockFile::LockType::Exclusive)
+ : m_lockFile(lockFile)
{
- public:
- LockFileGuard(LockFile& lockFile, LockFile::LockType lockType = LockFile::LockType::Exclusive)
- : m_lockFile(lockFile)
- {
- m_lockFile.lock(lockType);
- }
+ m_lockFile.lock(lockType);
+ }
- ~LockFileGuard()
- {
- m_lockFile.unlock();
- }
+ ~LockFileGuard() { m_lockFile.unlock(); }
- private:
- LockFileGuard(const LockFileGuard&) = delete;
- LockFileGuard(LockFileGuard&&) = delete;
- LockFileGuard& operator=(const LockFileGuard&) = delete;
- LockFileGuard& operator=(LockFileGuard&&) = delete;
+private:
+ LockFileGuard(const LockFileGuard&) = delete;
+ LockFileGuard(LockFileGuard&&) = delete;
+ LockFileGuard& operator=(const LockFileGuard&) = delete;
+ LockFileGuard& operator=(LockFileGuard&&) = delete;
- LockFile& m_lockFile;
- };
+ LockFile& m_lockFile;
+};
-}
+} // namespace Slang
#endif