diff --git a/SConstruct b/SConstruct index 48b0d84..4f4d44c 100644 --- a/SConstruct +++ b/SConstruct @@ -1,12 +1,11 @@ # SConstruct # vim: set ft=python: +# +# Toplevel Scons build script. This should be mostly complete and generic enough +# for most builds. +# # Eryn Wells -''' -Toplevel Scons build script. This should be mostly complete and generic enough -for most builds. -''' - import logging setup_logging() diff --git a/lib/core/SConscript b/lib/core/SConscript new file mode 100644 index 0000000..024769d --- /dev/null +++ b/lib/core/SConscript @@ -0,0 +1,4 @@ +# SConscript +# Eryn Wells + +Library('erw', ['src/File.cc']) diff --git a/lib/core/include/core/File.hh b/lib/core/include/core/File.hh new file mode 100644 index 0000000..d47ae88 --- /dev/null +++ b/lib/core/include/core/File.hh @@ -0,0 +1,112 @@ +/* File.hh + * vim: set tw=80: + * Eryn Wells + */ +/** + * File interface. + */ + +#include +#include +#include + +#include "String.hh" + + +namespace erw { + +struct File +{ + enum class SeekFrom { + /** Seek from the beginning of the file. */ + Begin, + /** Seek from the current file offset. */ + Here, + /** Seek from the end of the file. */ + End, + }; + + typedef std::bitset<3> Mode; + + static constexpr Mode In = Mode(1); + static constexpr Mode Out = Mode(2); + static constexpr Mode Binary = Mode(4); + + /** Destructor. */ + virtual ~File(); + + String path() const noexcept; + + /** Seek to an absolute position in the file. */ + virtual File& seek(size_t pos) = 0; + + /** + * Seek to an `offset` from the given anchor point in the file. + * @see SeekFrom + */ + virtual File& seek(ssize_t offset, SeekFrom from) = 0; + +protected: + const String mPath; + + /** Convert a File::Mode to an iostream openmode bitset. */ + virtual std::ios_base::openmode modeToIOSMode(Mode mode); + + File(const String& path); +}; + + +/** File handle for reading. */ +struct InFile + : public File +{ + /** Open a file at `path` for reading. */ + InFile(const String& path, File::Mode mode); + + /** Deleted copy constructor. File handles cannot be copied. */ + InFile(const InFile& other) = delete; + + /** Move `other` to this InFile. */ + InFile(InFile&& other); + + virtual ~InFile(); + + /** Move `other` to this InFile. File handles cannot be copied. */ + InFile& operator=(InFile& other); + + String path() const; + + /** Read up to `count` characters into the provided `buffer`. */ + InFile& read(char* buffer, ssize_t count); + + /** @see File::seek */ + InFile& seek(size_t pos) override; + + /** @See File::seek */ + InFile& seek(ssize_t pos, File::SeekFrom from) override; + +private: + String mPath; + std::ifstream mStream; + + std::ios_base::openmode modeToIOSMode(File::Mode mode) override; +}; + + +struct OutFile + : public File +{ + /** Write `count` characters from the provided `buffer` into this file. */ + InFile& write(char* buffer, ssize_t count); + + /** @see File::seek */ + InFile& seek(size_t pos) override; + + /** @See File::seek */ + InFile& seek(ssize_t pos, File::SeekFrom from) override; + +private: + std::ofstream stream; +}; + +} /* namespace erw */ diff --git a/lib/core/include/core/String.hh b/lib/core/include/core/String.hh new file mode 100644 index 0000000..2e65fe4 --- /dev/null +++ b/lib/core/include/core/String.hh @@ -0,0 +1,16 @@ +/* String.hh + * vim: set tw=80: + * Eryn Wells + */ +/** + * Strings are fun. + */ + +#include + + +namespace erw { + +typedef std::string String; + +} /* namespace erw */ diff --git a/lib/core/include/core/xml/Document.hh b/lib/core/include/core/xml/Document.hh new file mode 100644 index 0000000..bbccfa8 --- /dev/null +++ b/lib/core/include/core/xml/Document.hh @@ -0,0 +1,47 @@ +/* XMLDocument.hh + * vim: set tw=80: + * Eryn Wells + */ +/** + * An XML document and related structures. + */ + +#pragma once + +#include +#include +#include + +#include "core/File.hh" +#include "core/String.hh" + + +namespace erw { +namespace xml { + +struct Document; + + +/** An XML document. */ +struct Document +{ + /** + * Constructor. Parse an XML document out of the given file. Doing so takes + * ownership of the file. + */ + Document(InFile&& file); + + /** Constructor. Parse an XML document from the given string. */ + Document(const String& string); + + ~Document(); + + const Node& root() const noexcept; + +protected: + /** The root of the XML tree. The document owns its root. */ + Node mRoot; +}; + +} /* namespace xml */ +} /* namespace erw */ diff --git a/lib/core/include/core/xml/Node.hh b/lib/core/include/core/xml/Node.hh new file mode 100644 index 0000000..3ea27f7 --- /dev/null +++ b/lib/core/include/core/xml/Node.hh @@ -0,0 +1,41 @@ +/* Node.hh + * vim: set tw=80: + * Eryn Wells + */ +/** + * An XML node. + */ + +#pragma once + +#include +#include + +#include "String.hh" + +namespace erw { +namespace xml { + +/** A node in an XML tree. */ +struct Node +{ + typedef std::vector List; + typedef std::map AttributeMap; + + Node(); + Node(const String& name, const List& children); + Node(const Node& other); + ~Node(); + + String name() const noexcept; + List children() const noexcept; + +protected: + /** The name of the node. */ + String mName; + /** Children of this node. The node owns its children. */ + List mChildren; +}; + +} /* namespace xml */ +} /* namespace erw */ diff --git a/lib/core/src/File.cc b/lib/core/src/File.cc new file mode 100644 index 0000000..3fa3430 --- /dev/null +++ b/lib/core/src/File.cc @@ -0,0 +1,162 @@ +/* File.cc + * vim: set tw=80: + * Eryn Wells + */ +/** + * Implementation of file handling. + */ + +#include "core/File.hh" + + +namespace erw { + +#pragma mark - File + +/* + * File::File -- + */ +File::File(const String& path) + : mPath(path) +{ } + + +/* + * File::~File -- + */ +File::~File() +{ } + + +/* + * File::path -- + */ +String +File::path() + const noexcept +{ + return mPath; +} + + +/* + * File::modeToIOSMode -- + */ +std::ios_base::openmode +File::modeToIOSMode(Mode mode) +{ + std::ios_base::openmode openmode = 0; + if ((mode & In).any()) { + openmode |= std::ios_base::in; + } + if ((mode & Out).any()) { + openmode |= std::ios_base::out; + } + if ((mode & Binary).any()) { + openmode |= std::ios_base::binary; + } + return openmode; +} + +#pragma mark - InFile + +/* + * InFile::InFile -- + */ +InFile::InFile(const String& path, + InFile::Mode mode) + : File(path), + mStream(path, modeToIOSMode(mode)) +{ } + + +/* + * InFile::InFile -- + */ +InFile::InFile(InFile&& other) + : File(other.mPath), + mStream(std::move(other.mStream)) +{ } + + +/* + * InFile::~InFile -- + */ +InFile::~InFile() +{ } + + +/* + * InFile::operator= -- + */ +InFile& +InFile::operator=(InFile& other) +{ + mStream = std::move(other.mStream); + return *this; +} + + +/* + * InFile::read -- + */ +InFile& +InFile::read(char* buffer, + ssize_t count) +{ + mStream.read(buffer, count); + return *this; +} + + +/* + * InFile::seek -- + */ +InFile& +InFile::seek(size_t pos) +{ + mStream.seekg(pos); + return *this; +} + + +/* + * InFile::seek -- + */ +InFile& +InFile::seek(ssize_t off, + File::SeekFrom from) +{ + std::ios_base::seekdir dir; + switch (from) { + case File::SeekFrom::Begin: + dir = std::ios_base::beg; + break; + case File::SeekFrom::Here: + dir = std::ios_base::cur; + break; + case File::SeekFrom::End: + dir = std::ios_base::end; + break; + } + mStream.seekg(off, dir); + return *this; +} + + +/* + * InFile::modeToIOSMode -- + */ +std::ios_base::openmode +InFile::modeToIOSMode(File::Mode mode) +{ + // Ensure In flag is always set. + if (!(mode & File::In).any()) { + mode |= File::In; + } + return File::modeToIOSMode(mode); +} + +#pragma mark - OutFile + +} /* namespace erw */ diff --git a/lib/core/src/xml/Document.cc b/lib/core/src/xml/Document.cc new file mode 100644 index 0000000..53829e9 --- /dev/null +++ b/lib/core/src/xml/Document.cc @@ -0,0 +1,275 @@ +/* XMLParser.cc + * vim: set tw=80: + * Eryn Wells + */ +/** + * Implementation of an XML parser. + */ + +#include +#include + +#include +#include + +#include "XMLParser.hh" + + + +namespace { + +/* + * initLibrary -- + */ +void +initLibrary() +{ + static std::once_flag once; + std::call_once(once, []() { + xmlInitParser(); + }); +} + + +/* + * parseFile -- + */ +XMLNode +parseFile(File&& file) +{ + const size_t kInitialBufferSize = 16; + const size_t kBufferSize = 1024; + + static_assert(kInitialBufferSize < kBufferSize, + "XML parser initial buffer size must be smaller than the " + "total buffer size"); + + initLibrary(); + + char buffer[kBufferSize]; + ssize_t bytesRead = 0; + + bytesRead = file.read(buffer, kInitialBufferSize); + xmlParserCtxtPtr context = xmlCreatePushParserCtxt(nullptr, nullptr, + buffer, bytesRead, + file.path().c_str()); + if (!context) { + // TODO: Throw an appropriate error... + throw 42; + } + + /* + * Read chunks until we're done. Once all data has been read, indicate that + * the parser should terminate by calling xmlParseChunk() with a last + * argument of 1 rather than 0. + */ + while ((bytesRead = file.read(buffer, kBufferSize)) > 0) { + xmlParseChunk(context, buffer, bytesRead, 0); + } + xmlParseChunk(context, buffer, 0, 1); + + bool succeeded = bool(context->wellFormed); + xmlDocPtr document = context->myDoc; + + xmlFreeParserCtxt(context); + + return succeeded ? XMLDocument::UCPtr(new XML2Document(document)) : nullptr; +} + +} + + +namespace erw { + +struct XML2Node + : public XMLNode +{ + XML2Node(xmlNodePtr node); + + virtual ~XML2Node(); + + virtual String name() const noexcept override; + virtual String content() const noexcept override; + virtual XMLNode::AttributeMap attributes() const noexcept override; + +private: + xmlNodePtr mNode; + + /** Make a list of children of the node, excluding text nodes. */ + XMLNode::List childrenOfXML2Node(xmlNodePtr node) const noexcept; +}; + + +/** An XML parser that uses libxml2. */ +struct XML2Document + : public XMLDocument +{ + /** + * Initialize the xml2 library, if needed. This function may be called more + * than once with no adverse affects. + */ + static void initLibrary(); + + /** + * Parse a file into a libxml2 document object. Note: because of move + * semantics related to the file UPtr, the file will be closed after this + * method completes. + * + * @param [in] file The file to parse. + * @return A libxml2 document, or nullptr if the parse fails. + */ + static XMLDocument::UCPtr parseFile(File::UPtr file); + + static XMLDocument::UCPtr parseString(const String& string); + + XML2Document(xmlDocPtr document); + + virtual ~XML2Document(); + + virtual XMLNode::WCPtr root() const noexcept override; + +private: + xmlDocPtr mDocument; +}; + +#pragma mark - erw::XMLDocument + +/* static */ XMLDocument::UCPtr +XMLDocument::parseFile(File::UPtr file) +{ + return XML2Document::parseFile(std::move(file)); +} + + +/* static */ XMLDocument::UCPtr +XMLDocument::parseString(const String& string) +{ + return XML2Document::parseString(string); +} + + +XMLDocument(InFile&& file) + : mRoot() +{ } + + +XMLDocument::~XMLDocument() +{ } + +#pragma mark - erw::XML2Document + + + +/* static */ XMLDocument::UCPtr +XML2Document::parseString(const String& string) +{ + initLibrary(); + xmlDocPtr document = xmlReadMemory(string.c_str(), string.size(), "memory.xml", NULL, 0); + return document ? XMLDocument::UCPtr(new XML2Document(document)) : nullptr; +} + + +XML2Document::XML2Document(xmlDocPtr document) + : XMLDocument(XMLNode::Ptr(new XML2Node(xmlDocGetRootElement(document)))), + mDocument(document) +{ } + + +XML2Document::~XML2Document() +{ + if (mDocument) { + xmlFreeDoc(mDocument); + mDocument = nullptr; + } +} + + +XMLNode::WCPtr +XML2Document::root() + const noexcept +{ + return mRoot; +} + +#pragma mark - XMLNode + +XMLNode::~XMLNode() +{ } + + +XMLNode::XMLNode(XMLNode::List&& children) + : mChildren(children) +{ } + + +XMLNode::WCList +XMLNode::children() + const noexcept +{ + WCList weakChildren; + for (Ptr child : mChildren) { + weakChildren.push_back(WCPtr(child)); + } + return weakChildren; +} + +#pragma mark - XML2Node + +XML2Node::XML2Node(xmlNodePtr node) + : XMLNode(childrenOfXML2Node(node)), + mNode(node) +{ } + + +XML2Node::~XML2Node() +{ } + + +String +XML2Node::name() + const noexcept +{ + return (const char *)mNode->name; +} + + +String +XML2Node::content() + const noexcept +{ + xmlChar *content = xmlNodeGetContent(mNode); + String contentString((const char *)content); + xmlFree(content); + return contentString; +} + + +XMLNode::AttributeMap +XML2Node::attributes() + const noexcept +{ + AttributeMap attrs; + for (xmlAttrPtr attr = mNode->properties; attr && attr->name && attr->children; attr = attr->next) { + xmlChar *value = xmlNodeListGetString(mNode->doc, attr->children, 1); + attrs[(const char *)attr->name] = (const char *)value; + xmlFree(value); + } + return attrs; +} + + +XMLNode::List +XML2Node::childrenOfXML2Node(xmlNodePtr node) + const noexcept +{ + XMLNode::List children; + for (xmlNodePtr c = node->children; c != nullptr; c = c->next) { + if (c->type != XML_ELEMENT_NODE) { + continue; + } + children.push_back(XMLNode::Ptr(new XML2Node(c))); + } + return children; +} + +} /* namespace erw */ diff --git a/lib/core/src/xml/Node.cc b/lib/core/src/xml/Node.cc new file mode 100644 index 0000000..7517de0 --- /dev/null +++ b/lib/core/src/xml/Node.cc @@ -0,0 +1,76 @@ +/* XMLNode.cc + * vim: set tw=80: + * Eryn Wells + */ +/** + * Implementation of a node in an XML tree. + */ + +#include "core/XMLDocument.hh" + +#include +#include + + +namespace erw { +namespace xml { + +/* + * Node::Node -- + */ +Node::Node() + : mName(), + mChildren() +{ } + + +/* + * Node::Node -- + */ +Node::Node(const String& name, + const List& children) + : mName(name), + mChildren(children) +{ } + + +/* + * Node::Node -- + */ +Node::Node(const Node& other) + : mName(other.name), + mChildren(other.children) +{ } + + +/* + * Node::~Node -- + */ +Node::~Node() +{ } + +#pragma mark Properties + +/* + * Node::name -- + */ +String +Node::name() + const +{ + return mName; +} + + +/* + * Node::children -- + */ +List +Node::children() + const +{ + return mChildren; +} + +} /* namespace xml */ +} /* namespace erw */ diff --git a/site_scons/paths.py b/site_scons/paths.py new file mode 100644 index 0000000..c2b37b9 --- /dev/null +++ b/site_scons/paths.py @@ -0,0 +1,30 @@ +# paths.py +# Eryn Wells + +import os +import os.path + + +def is_executable(path): + return os.path.exists(path) and os.access(path, os.X_OK) + + +def which(program): + ''' + Look for `program` in system path and return the full path to that binary if + it is found. Otherwise, return `None`. + ''' + path, name = os.path.split(program) + if path: + if is_executable(program): + return program + else: + pathext = [''] + os.environ.get('PATHEXT', '').split(os.pathsep) + for path in os.environ.get('PATH', '').split(os.pathsep): + exe = os.path.join(path, program) + for ext in pathext: + candidate = exe + ext + if is_executable(candidate): + return candidate + return None + diff --git a/site_scons/site_init.py b/site_scons/site_init.py index 0c2f2ba..cb25ecf 100644 --- a/site_scons/site_init.py +++ b/site_scons/site_init.py @@ -3,9 +3,12 @@ import logging import sys + import SCons.Environment import SCons.Errors +import paths + def setup_logging(level=logging.DEBUG): '''Configure global logging for the SCons system.''' @@ -102,9 +105,6 @@ class Environment(SCons.Environment.Environment): if colorful and sys.stdout.isatty(): if 'clang' in self['CC'] or 'clang' in self['CXX']: self.AppendUnique(CCFLAGS=['-fcolor-diagnostics']) - elif 'gcc' in self['CC'] or 'g++' in self['CXX']: - # TODO: Also set a GCC_COLORS variable in the system environment? - self.AppendUnique(CCFLAGS=['-fdiagnostics-color=always']) # Pretty printing self.SetDefault(ARCOMSTR=Environment._comstr('Archiving', succinct)) @@ -135,6 +135,8 @@ class Environment(SCons.Environment.Environment): def process_src(self): out_dir = self.build_root.Dir('src') + # TODO: Do the thing. + # do_sconscript(env, env.source_root, src_out_dir) self.SConscript(self.src_root.File('SConscript'), variant_dir=out_dir) self.Append(CPPPATH=[self.src_root]) @@ -182,7 +184,7 @@ class Environment(SCons.Environment.Environment): def _append_custom_tools(self, kwargs): '''Add custom tools to the `kwargs`.''' tools = kwargs.setdefault('tools', ['default']) - for tool in ['lib', 'test', 'program', 'sconscript', 'swiftc']: + for tool in ['lib', 'test', 'program', 'sconscript']: if tool in tools: continue tools.append(tool) diff --git a/site_scons/site_tools/sconscript.py b/site_scons/site_tools/sconscript.py index 3e9b597..b898481 100644 --- a/site_scons/site_tools/sconscript.py +++ b/site_scons/site_tools/sconscript.py @@ -9,20 +9,18 @@ import SCons.Script def _do_sconscript(env): original_sconscript = env.SConscript - + def sconscript(env, sconscript, clone=False, *args, **kwargs): - exports = { - 'Library': env.Library, - 'Object': env.Object, - 'SharedObject': env.SharedObject, - 'StaticLibrary': env.StaticLibrary, - 'SharedLibrary': env.SharedLibrary, - 'Program': env.Program, - 'env': env.Clone() if clone else env, - } + exports = {'Library': env.Library, + 'StaticLibrary': env.StaticLibrary, + 'SharedLibrary': env.SharedLibrary, + 'Program': env.Program} SCons.Script._SConscript.GlobalDict.update(exports) env.log('Reading {}'.format(sconscript)) - return original_sconscript(sconscript, {}, *args, **kwargs) + return original_sconscript(sconscript, + {'env': env.Clone() if clone else env}, + *args, + **kwargs) return sconscript diff --git a/site_scons/site_tools/swiftc.py b/site_scons/site_tools/swiftc.py deleted file mode 100644 index 5d2766d..0000000 --- a/site_scons/site_tools/swiftc.py +++ /dev/null @@ -1,33 +0,0 @@ -# swiftc.py -# vim: set ft=python: -# Eryn Wells - -''' -SCons plugin for building Swift files with swiftc. -''' - -import SCons.Action -import SCons.Tool -import SCons.Util - -SwiftSuffix = '.swift' -SwiftAction = SCons.Action.Action("$SWIFTCCOM", "$SWIFTCCOMSTR") - -compilers = ['swiftc'] - -def generate(env): - static_obj, shared_obj = SCons.Tool.createObjBuilders(env) - static_obj.add_action(SwiftSuffix, SwiftAction) - static_obj.add_emitter(SwiftSuffix, SCons.Defaults.SharedObjectEmitter) - shared_obj.add_action(SwiftSuffix, SwiftAction) - shared_obj.add_emitter(SwiftSuffix, SCons.Defaults.SharedObjectEmitter) - - if 'SWIFTC' not in env: - compiler = env.Detect(compilers) - env['SWIFTC'] = compiler if compiler else compilers[0] - env['SWIFTFLAGS'] = SCons.Util.CLVar('') - env['SWIFTCCOM'] = '$SWIFTC -o $TARGET -c $SWIFTFLAGS $SOURCES' - env['SWIFTFILESUFFIX'] = SwiftSuffix - -def exists(env): - return env.Detect(compilers)