Compare commits

..

5 commits
master ... core

13 changed files with 782 additions and 53 deletions

View file

@ -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 <eryn@erynwells.me>
'''
Toplevel Scons build script. This should be mostly complete and generic enough
for most builds.
'''
import logging
setup_logging()

4
lib/core/SConscript Normal file
View file

@ -0,0 +1,4 @@
# SConscript
# Eryn Wells <eryn@erynwells.me>
Library('erw', ['src/File.cc'])

View file

@ -0,0 +1,112 @@
/* File.hh
* vim: set tw=80:
* Eryn Wells <eryn@erynwells.me>
*/
/**
* File interface.
*/
#include <bitset>
#include <fstream>
#include <memory>
#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 */

View file

@ -0,0 +1,16 @@
/* String.hh
* vim: set tw=80:
* Eryn Wells <eryn@erynwells.me>
*/
/**
* Strings are fun.
*/
#include <string>
namespace erw {
typedef std::string String;
} /* namespace erw */

View file

@ -0,0 +1,47 @@
/* XMLDocument.hh
* vim: set tw=80:
* Eryn Wells <eryn@erynwells.me>
*/
/**
* An XML document and related structures.
*/
#pragma once
#include <map>
#include <memory>
#include <vector>
#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 */

View file

@ -0,0 +1,41 @@
/* Node.hh
* vim: set tw=80:
* Eryn Wells <eryn@erynwells.me>
*/
/**
* An XML node.
*/
#pragma once
#include <map>
#include <vector>
#include "String.hh"
namespace erw {
namespace xml {
/** A node in an XML tree. */
struct Node
{
typedef std::vector<Node> List;
typedef std::map<String, String> 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 */

162
lib/core/src/File.cc Normal file
View file

@ -0,0 +1,162 @@
/* File.cc
* vim: set tw=80:
* Eryn Wells <eryn@erynwells.me>
*/
/**
* 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 */

View file

@ -0,0 +1,275 @@
/* XMLParser.cc
* vim: set tw=80:
* Eryn Wells <eryn@erynwells.me>
*/
/**
* Implementation of an XML parser.
*/
#include <cstddef>
#include <mutex>
#include <libxml/parser.h>
#include <libxml/tree.h>
#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 */

76
lib/core/src/xml/Node.cc Normal file
View file

@ -0,0 +1,76 @@
/* XMLNode.cc
* vim: set tw=80:
* Eryn Wells <eryn@erynwells.me>
*/
/**
* Implementation of a node in an XML tree.
*/
#include "core/XMLDocument.hh"
#include <libxml/parser.h>
#include <libxml/tree.h>
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 */

30
site_scons/paths.py Normal file
View file

@ -0,0 +1,30 @@
# paths.py
# Eryn Wells <eryn@erynwells.me>
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

View file

@ -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)

View file

@ -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

View file

@ -1,33 +0,0 @@
# swiftc.py
# vim: set ft=python:
# Eryn Wells <eryn@erynwells.me>
'''
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)